@push.rocks/smartsecret 1.0.1 → 1.1.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,57 @@
1
+ import * as plugins from './smartsecret.plugins.js';
2
+ export declare const SMARTSECRET_KEYRING_SCHEMA_VERSION: 1;
3
+ export declare const SMARTSECRET_KEYRING_PROFILE: "smartsecret-aes-256-gcm-dek-kek-v1";
4
+ export type TSmartSecretKeyringProfile = typeof SMARTSECRET_KEYRING_PROFILE;
5
+ export declare const SMARTSECRET_KEYRING_MAX_PAYLOAD_BYTES = 524288;
6
+ export declare const SMARTSECRET_KEYRING_MIN_CONTEXT_BYTES = 1;
7
+ export declare const SMARTSECRET_KEYRING_MAX_CONTEXT_BYTES = 65536;
8
+ export declare const SMARTSECRET_KEYRING_MAX_IDENTIFIER_BYTES = 200;
9
+ export declare const SMARTSECRET_KEYRING_MAX_KEK_VERSION = 2147483647;
10
+ export interface ISmartSecretEnvelopeCiphertext {
11
+ nonce: string;
12
+ ciphertext: string;
13
+ tag: string;
14
+ }
15
+ export interface ISmartSecretEnvelopeV1 {
16
+ schemaVersion: 1;
17
+ profile: TSmartSecretKeyringProfile;
18
+ envelopeId: string;
19
+ createdAt: number;
20
+ contextDigest: string;
21
+ kek: {
22
+ keyringId: string;
23
+ version: number;
24
+ fingerprint: `sha256:${string}`;
25
+ };
26
+ payload: ISmartSecretEnvelopeCiphertext;
27
+ wrappedDek: ISmartSecretEnvelopeCiphertext;
28
+ }
29
+ export interface IParsedSmartSecretEnvelopeV1 {
30
+ envelope: ISmartSecretEnvelopeV1;
31
+ contextDigest: Uint8Array;
32
+ kekFingerprint: Uint8Array;
33
+ payload: plugins.smartcrypto.IAesGcmCiphertext;
34
+ wrappedDek: plugins.smartcrypto.IAesGcmCiphertext;
35
+ }
36
+ export interface ISmartSecretWrapAadFields {
37
+ envelopeId: string;
38
+ createdAt: number;
39
+ contextDigest: Uint8Array;
40
+ payloadDigest: Uint8Array;
41
+ keyringId: string;
42
+ kekVersion: number;
43
+ kekFingerprint: Uint8Array;
44
+ }
45
+ export declare const requireSmartSecretIdentifier: (valueArg: unknown) => string;
46
+ export declare const requireEnvelopeIdentifier: (valueArg: unknown) => string;
47
+ export declare const requireKekVersion: (valueArg: unknown, envelopeArg?: boolean) => number;
48
+ export declare const requireEpochMilliseconds: (valueArg: unknown, envelopeArg?: boolean) => number;
49
+ export declare const requireFingerprint: (valueArg: unknown, envelopeArg?: boolean) => `sha256:${string}`;
50
+ export declare const fingerprintToBytes: (valueArg: `sha256:${string}`) => Uint8Array;
51
+ export declare const encodeCanonicalBase64Url: (valueArg: Uint8Array) => string;
52
+ export declare const parseSmartSecretEnvelope: (valueArg: unknown) => IParsedSmartSecretEnvelopeV1;
53
+ export declare const sha256Bytes: (...valuesArg: Uint8Array[]) => Uint8Array;
54
+ export declare const createSmartSecretPayloadDigest: (payloadArg: plugins.smartcrypto.IAesGcmCiphertext) => Uint8Array;
55
+ export declare const createSmartSecretWrapAad: (fieldsArg: ISmartSecretWrapAadFields) => Uint8Array;
56
+ export declare const wipeCiphertext: (valueArg: plugins.smartcrypto.IAesGcmCiphertext | undefined) => void;
57
+ export declare const timingSafeEqual: (leftArg: Uint8Array, rightArg: Uint8Array) => boolean;
@@ -0,0 +1,290 @@
1
+ import * as plugins from './smartsecret.plugins.js';
2
+ import { SmartSecretKeyringError, createSmartSecretKeyringError, } from './smartsecret.keyring.error.js';
3
+ export const SMARTSECRET_KEYRING_SCHEMA_VERSION = 1;
4
+ export const SMARTSECRET_KEYRING_PROFILE = 'smartsecret-aes-256-gcm-dek-kek-v1';
5
+ export const SMARTSECRET_KEYRING_MAX_PAYLOAD_BYTES = 524_288;
6
+ export const SMARTSECRET_KEYRING_MIN_CONTEXT_BYTES = 1;
7
+ export const SMARTSECRET_KEYRING_MAX_CONTEXT_BYTES = 65_536;
8
+ export const SMARTSECRET_KEYRING_MAX_IDENTIFIER_BYTES = 200;
9
+ export const SMARTSECRET_KEYRING_MAX_KEK_VERSION = 2_147_483_647;
10
+ const identifierPattern = /^[A-Za-z0-9][A-Za-z0-9:._-]{0,199}$/;
11
+ const fingerprintPattern = /^sha256:[0-9a-f]{64}$/;
12
+ const base64UrlPattern = /^[A-Za-z0-9_-]*$/;
13
+ const wrapAadMagic = Buffer.from('smartsecret-envelope-wrap-aad-v1', 'ascii');
14
+ const payloadDigestMagic = Buffer.from('smartsecret-envelope-payload-digest-v1', 'ascii');
15
+ const requirePlainObject = (valueArg, expectedKeysArg) => {
16
+ try {
17
+ if (valueArg === null || typeof valueArg !== 'object' || Array.isArray(valueArg)) {
18
+ throw createSmartSecretKeyringError('ENVELOPE_INVALID');
19
+ }
20
+ const prototype = Object.getPrototypeOf(valueArg);
21
+ if (prototype !== Object.prototype && prototype !== null) {
22
+ throw createSmartSecretKeyringError('ENVELOPE_INVALID');
23
+ }
24
+ const ownKeys = Reflect.ownKeys(valueArg);
25
+ if (ownKeys.length !== expectedKeysArg.length
26
+ || ownKeys.some((keyArg) => typeof keyArg !== 'string')) {
27
+ throw createSmartSecretKeyringError('ENVELOPE_INVALID');
28
+ }
29
+ const actualKeys = ownKeys;
30
+ if (expectedKeysArg.some((keyArg) => !actualKeys.includes(keyArg))) {
31
+ throw createSmartSecretKeyringError('ENVELOPE_INVALID');
32
+ }
33
+ const result = Object.create(null);
34
+ for (const key of actualKeys) {
35
+ const descriptor = Object.getOwnPropertyDescriptor(valueArg, key);
36
+ if (!descriptor?.enumerable || !('value' in descriptor)) {
37
+ throw createSmartSecretKeyringError('ENVELOPE_INVALID');
38
+ }
39
+ result[key] = descriptor.value;
40
+ }
41
+ return result;
42
+ }
43
+ catch (error) {
44
+ if (error instanceof SmartSecretKeyringError) {
45
+ throw error;
46
+ }
47
+ throw createSmartSecretKeyringError('ENVELOPE_INVALID');
48
+ }
49
+ };
50
+ export const requireSmartSecretIdentifier = (valueArg) => {
51
+ if (typeof valueArg !== 'string'
52
+ || !identifierPattern.test(valueArg)
53
+ || Buffer.byteLength(valueArg, 'utf8') < 1
54
+ || Buffer.byteLength(valueArg, 'utf8') > SMARTSECRET_KEYRING_MAX_IDENTIFIER_BYTES) {
55
+ throw createSmartSecretKeyringError('CONFIG_INVALID');
56
+ }
57
+ return valueArg;
58
+ };
59
+ export const requireEnvelopeIdentifier = (valueArg) => {
60
+ try {
61
+ return requireSmartSecretIdentifier(valueArg);
62
+ }
63
+ catch {
64
+ throw createSmartSecretKeyringError('ENVELOPE_INVALID');
65
+ }
66
+ };
67
+ export const requireKekVersion = (valueArg, envelopeArg = false) => {
68
+ if (!Number.isSafeInteger(valueArg)
69
+ || valueArg < 1
70
+ || valueArg > SMARTSECRET_KEYRING_MAX_KEK_VERSION) {
71
+ throw createSmartSecretKeyringError(envelopeArg ? 'ENVELOPE_INVALID' : 'CONFIG_INVALID');
72
+ }
73
+ return valueArg;
74
+ };
75
+ export const requireEpochMilliseconds = (valueArg, envelopeArg = false) => {
76
+ if (!Number.isSafeInteger(valueArg) || valueArg < 0) {
77
+ throw createSmartSecretKeyringError(envelopeArg ? 'ENVELOPE_INVALID' : 'CONFIG_INVALID');
78
+ }
79
+ return valueArg;
80
+ };
81
+ export const requireFingerprint = (valueArg, envelopeArg = false) => {
82
+ if (typeof valueArg !== 'string' || !fingerprintPattern.test(valueArg)) {
83
+ throw createSmartSecretKeyringError(envelopeArg ? 'ENVELOPE_INVALID' : 'CONFIG_INVALID');
84
+ }
85
+ return valueArg;
86
+ };
87
+ export const fingerprintToBytes = (valueArg) => new Uint8Array(Buffer.from(valueArg.slice('sha256:'.length), 'hex'));
88
+ export const encodeCanonicalBase64Url = (valueArg) => Buffer.from(valueArg).toString('base64url');
89
+ const decodeCanonicalBase64Url = (valueArg, maximumBytesArg, exactBytesArg) => {
90
+ if (typeof valueArg !== 'string') {
91
+ throw createSmartSecretKeyringError('ENVELOPE_INVALID');
92
+ }
93
+ const maximumEncodedLength = Math.ceil(maximumBytesArg * 4 / 3);
94
+ if (valueArg.length > maximumEncodedLength) {
95
+ throw createSmartSecretKeyringError('SIZE_LIMIT_EXCEEDED');
96
+ }
97
+ if (!base64UrlPattern.test(valueArg) || valueArg.length % 4 === 1) {
98
+ throw createSmartSecretKeyringError('ENVELOPE_INVALID');
99
+ }
100
+ let result;
101
+ let decodedBuffer;
102
+ let didSucceed = false;
103
+ try {
104
+ decodedBuffer = Buffer.from(valueArg, 'base64url');
105
+ result = new Uint8Array(decodedBuffer);
106
+ if (result.byteLength > maximumBytesArg) {
107
+ throw createSmartSecretKeyringError('SIZE_LIMIT_EXCEEDED');
108
+ }
109
+ if (exactBytesArg !== undefined && result.byteLength !== exactBytesArg) {
110
+ throw createSmartSecretKeyringError('ENVELOPE_INVALID');
111
+ }
112
+ if (encodeCanonicalBase64Url(result) !== valueArg) {
113
+ throw createSmartSecretKeyringError('ENVELOPE_INVALID');
114
+ }
115
+ didSucceed = true;
116
+ return result;
117
+ }
118
+ catch (error) {
119
+ if (error instanceof SmartSecretKeyringError) {
120
+ throw error;
121
+ }
122
+ throw createSmartSecretKeyringError('ENVELOPE_INVALID');
123
+ }
124
+ finally {
125
+ decodedBuffer?.fill(0);
126
+ if (!didSucceed) {
127
+ result?.fill(0);
128
+ }
129
+ }
130
+ };
131
+ const parseCiphertext = (valueArg, maximumCiphertextBytesArg, exactCiphertextBytesArg) => {
132
+ const value = requirePlainObject(valueArg, ['nonce', 'ciphertext', 'tag']);
133
+ let nonce;
134
+ let ciphertext;
135
+ let tag;
136
+ let didSucceed = false;
137
+ try {
138
+ nonce = decodeCanonicalBase64Url(value.nonce, 12, 12);
139
+ ciphertext = decodeCanonicalBase64Url(value.ciphertext, maximumCiphertextBytesArg, exactCiphertextBytesArg);
140
+ tag = decodeCanonicalBase64Url(value.tag, 16, 16);
141
+ const result = {
142
+ encoded: {
143
+ nonce: value.nonce,
144
+ ciphertext: value.ciphertext,
145
+ tag: value.tag,
146
+ },
147
+ raw: { nonce, ciphertext, tag },
148
+ };
149
+ didSucceed = true;
150
+ return result;
151
+ }
152
+ finally {
153
+ if (!didSucceed) {
154
+ nonce?.fill(0);
155
+ ciphertext?.fill(0);
156
+ tag?.fill(0);
157
+ }
158
+ }
159
+ };
160
+ export const parseSmartSecretEnvelope = (valueArg) => {
161
+ const value = requirePlainObject(valueArg, [
162
+ 'schemaVersion',
163
+ 'profile',
164
+ 'envelopeId',
165
+ 'createdAt',
166
+ 'contextDigest',
167
+ 'kek',
168
+ 'payload',
169
+ 'wrappedDek',
170
+ ]);
171
+ if (value.schemaVersion !== SMARTSECRET_KEYRING_SCHEMA_VERSION) {
172
+ throw createSmartSecretKeyringError('ENVELOPE_INVALID');
173
+ }
174
+ if (value.profile !== SMARTSECRET_KEYRING_PROFILE) {
175
+ throw createSmartSecretKeyringError('ENVELOPE_INVALID');
176
+ }
177
+ let contextDigest;
178
+ let kekFingerprint;
179
+ let payload;
180
+ let wrappedDek;
181
+ let didSucceed = false;
182
+ try {
183
+ const envelopeId = requireEnvelopeIdentifier(value.envelopeId);
184
+ const createdAt = requireEpochMilliseconds(value.createdAt, true);
185
+ contextDigest = decodeCanonicalBase64Url(value.contextDigest, 32, 32);
186
+ const kekValue = requirePlainObject(value.kek, ['keyringId', 'version', 'fingerprint']);
187
+ const keyringId = requireEnvelopeIdentifier(kekValue.keyringId);
188
+ const version = requireKekVersion(kekValue.version, true);
189
+ const fingerprint = requireFingerprint(kekValue.fingerprint, true);
190
+ kekFingerprint = fingerprintToBytes(fingerprint);
191
+ payload = parseCiphertext(value.payload, SMARTSECRET_KEYRING_MAX_PAYLOAD_BYTES);
192
+ wrappedDek = parseCiphertext(value.wrappedDek, 32, 32);
193
+ const result = {
194
+ envelope: {
195
+ schemaVersion: SMARTSECRET_KEYRING_SCHEMA_VERSION,
196
+ profile: SMARTSECRET_KEYRING_PROFILE,
197
+ envelopeId,
198
+ createdAt,
199
+ contextDigest: value.contextDigest,
200
+ kek: { keyringId, version, fingerprint },
201
+ payload: payload.encoded,
202
+ wrappedDek: wrappedDek.encoded,
203
+ },
204
+ contextDigest,
205
+ kekFingerprint,
206
+ payload: payload.raw,
207
+ wrappedDek: wrappedDek.raw,
208
+ };
209
+ didSucceed = true;
210
+ return result;
211
+ }
212
+ finally {
213
+ if (!didSucceed) {
214
+ contextDigest?.fill(0);
215
+ kekFingerprint?.fill(0);
216
+ wipeCiphertext(payload?.raw);
217
+ wipeCiphertext(wrappedDek?.raw);
218
+ }
219
+ }
220
+ };
221
+ export const sha256Bytes = (...valuesArg) => {
222
+ const hash = plugins.crypto.createHash('sha256');
223
+ for (const value of valuesArg) {
224
+ hash.update(value);
225
+ }
226
+ return new Uint8Array(hash.digest());
227
+ };
228
+ const uint32Bytes = (valueArg) => {
229
+ const result = Buffer.allocUnsafe(4);
230
+ result.writeUInt32BE(valueArg, 0);
231
+ return result;
232
+ };
233
+ const uint64Bytes = (valueArg) => {
234
+ const result = Buffer.allocUnsafe(8);
235
+ result.writeBigUInt64BE(BigInt(valueArg), 0);
236
+ return result;
237
+ };
238
+ const lengthPrefixed = (valueArg) => {
239
+ if (valueArg.byteLength > 0xffff_ffff) {
240
+ throw createSmartSecretKeyringError('SIZE_LIMIT_EXCEEDED');
241
+ }
242
+ return Buffer.concat([uint32Bytes(valueArg.byteLength), valueArg]);
243
+ };
244
+ export const createSmartSecretPayloadDigest = (payloadArg) => {
245
+ const hash = plugins.crypto.createHash('sha256');
246
+ hash.update(payloadDigestMagic);
247
+ for (const value of [payloadArg.nonce, payloadArg.ciphertext, payloadArg.tag]) {
248
+ const length = uint32Bytes(value.byteLength);
249
+ try {
250
+ hash.update(length);
251
+ hash.update(value);
252
+ }
253
+ finally {
254
+ length.fill(0);
255
+ }
256
+ }
257
+ return new Uint8Array(hash.digest());
258
+ };
259
+ export const createSmartSecretWrapAad = (fieldsArg) => {
260
+ if (fieldsArg.contextDigest.byteLength !== 32
261
+ || fieldsArg.payloadDigest.byteLength !== 32
262
+ || fieldsArg.kekFingerprint.byteLength !== 32) {
263
+ throw createSmartSecretKeyringError('ENVELOPE_INVALID');
264
+ }
265
+ const profile = Buffer.from(SMARTSECRET_KEYRING_PROFILE, 'utf8');
266
+ const envelopeId = Buffer.from(requireEnvelopeIdentifier(fieldsArg.envelopeId), 'utf8');
267
+ const keyringId = Buffer.from(requireEnvelopeIdentifier(fieldsArg.keyringId), 'utf8');
268
+ const createdAt = requireEpochMilliseconds(fieldsArg.createdAt, true);
269
+ const kekVersion = requireKekVersion(fieldsArg.kekVersion, true);
270
+ return Buffer.concat([
271
+ wrapAadMagic,
272
+ uint64Bytes(SMARTSECRET_KEYRING_SCHEMA_VERSION),
273
+ lengthPrefixed(profile),
274
+ lengthPrefixed(envelopeId),
275
+ uint64Bytes(createdAt),
276
+ fieldsArg.contextDigest,
277
+ fieldsArg.payloadDigest,
278
+ lengthPrefixed(keyringId),
279
+ uint64Bytes(kekVersion),
280
+ fieldsArg.kekFingerprint,
281
+ ]);
282
+ };
283
+ export const wipeCiphertext = (valueArg) => {
284
+ valueArg?.nonce.fill(0);
285
+ valueArg?.ciphertext.fill(0);
286
+ valueArg?.tag.fill(0);
287
+ };
288
+ export const timingSafeEqual = (leftArg, rightArg) => leftArg.byteLength === rightArg.byteLength
289
+ && plugins.crypto.timingSafeEqual(leftArg, rightArg);
290
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoic21hcnRzZWNyZXQua2V5cmluZy5wcm90b2NvbC5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uL3RzL3NtYXJ0c2VjcmV0LmtleXJpbmcucHJvdG9jb2wudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUEsT0FBTyxLQUFLLE9BQU8sTUFBTSwwQkFBMEIsQ0FBQztBQUNwRCxPQUFPLEVBQ0wsdUJBQXVCLEVBQ3ZCLDZCQUE2QixHQUM5QixNQUFNLGdDQUFnQyxDQUFDO0FBRXhDLE1BQU0sQ0FBQyxNQUFNLGtDQUFrQyxHQUFHLENBQVUsQ0FBQztBQUM3RCxNQUFNLENBQUMsTUFBTSwyQkFBMkIsR0FBRyxvQ0FBNkMsQ0FBQztBQUd6RixNQUFNLENBQUMsTUFBTSxxQ0FBcUMsR0FBRyxPQUFPLENBQUM7QUFDN0QsTUFBTSxDQUFDLE1BQU0scUNBQXFDLEdBQUcsQ0FBQyxDQUFDO0FBQ3ZELE1BQU0sQ0FBQyxNQUFNLHFDQUFxQyxHQUFHLE1BQU0sQ0FBQztBQUM1RCxNQUFNLENBQUMsTUFBTSx3Q0FBd0MsR0FBRyxHQUFHLENBQUM7QUFDNUQsTUFBTSxDQUFDLE1BQU0sbUNBQW1DLEdBQUcsYUFBYSxDQUFDO0FBRWpFLE1BQU0saUJBQWlCLEdBQUcscUNBQXFDLENBQUM7QUFDaEUsTUFBTSxrQkFBa0IsR0FBRyx1QkFBdUIsQ0FBQztBQUNuRCxNQUFNLGdCQUFnQixHQUFHLGtCQUFrQixDQUFDO0FBQzVDLE1BQU0sWUFBWSxHQUFHLE1BQU0sQ0FBQyxJQUFJLENBQUMsa0NBQWtDLEVBQUUsT0FBTyxDQUFDLENBQUM7QUFDOUUsTUFBTSxrQkFBa0IsR0FBRyxNQUFNLENBQUMsSUFBSSxDQUFDLHdDQUF3QyxFQUFFLE9BQU8sQ0FBQyxDQUFDO0FBeUMxRixNQUFNLGtCQUFrQixHQUFHLENBQ3pCLFFBQWlCLEVBQ2pCLGVBQWtDLEVBQ1QsRUFBRTtJQUMzQixJQUFJLENBQUM7UUFDSCxJQUFJLFFBQVEsS0FBSyxJQUFJLElBQUksT0FBTyxRQUFR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
@@ -3,4 +3,5 @@ import * as crypto from 'crypto';
3
3
  import * as fs from 'fs';
4
4
  import * as os from 'os';
5
5
  import * as path from 'path';
6
- export { childProcess, crypto, fs, os, path };
6
+ import * as smartcrypto from '@push.rocks/smartcrypto';
7
+ export { childProcess, crypto, fs, os, path, smartcrypto };
@@ -4,5 +4,7 @@ import * as crypto from 'crypto';
4
4
  import * as fs from 'fs';
5
5
  import * as os from 'os';
6
6
  import * as path from 'path';
7
- export { childProcess, crypto, fs, os, path };
8
- //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoic21hcnRzZWNyZXQucGx1Z2lucy5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uL3RzL3NtYXJ0c2VjcmV0LnBsdWdpbnMudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUEsb0JBQW9CO0FBQ3BCLE9BQU8sS0FBSyxZQUFZLE1BQU0sZUFBZSxDQUFDO0FBQzlDLE9BQU8sS0FBSyxNQUFNLE1BQU0sUUFBUSxDQUFDO0FBQ2pDLE9BQU8sS0FBSyxFQUFFLE1BQU0sSUFBSSxDQUFDO0FBQ3pCLE9BQU8sS0FBSyxFQUFFLE1BQU0sSUFBSSxDQUFDO0FBQ3pCLE9BQU8sS0FBSyxJQUFJLE1BQU0sTUFBTSxDQUFDO0FBRTdCLE9BQU8sRUFBRSxZQUFZLEVBQUUsTUFBTSxFQUFFLEVBQUUsRUFBRSxFQUFFLEVBQUUsSUFBSSxFQUFFLENBQUMifQ==
7
+ // @push.rocks scope
8
+ import * as smartcrypto from '@push.rocks/smartcrypto';
9
+ export { childProcess, crypto, fs, os, path, smartcrypto };
10
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoic21hcnRzZWNyZXQucGx1Z2lucy5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uL3RzL3NtYXJ0c2VjcmV0LnBsdWdpbnMudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUEsb0JBQW9CO0FBQ3BCLE9BQU8sS0FBSyxZQUFZLE1BQU0sZUFBZSxDQUFDO0FBQzlDLE9BQU8sS0FBSyxNQUFNLE1BQU0sUUFBUSxDQUFDO0FBQ2pDLE9BQU8sS0FBSyxFQUFFLE1BQU0sSUFBSSxDQUFDO0FBQ3pCLE9BQU8sS0FBSyxFQUFFLE1BQU0sSUFBSSxDQUFDO0FBQ3pCLE9BQU8sS0FBSyxJQUFJLE1BQU0sTUFBTSxDQUFDO0FBRTdCLG9CQUFvQjtBQUNwQixPQUFPLEtBQUssV0FBVyxNQUFNLHlCQUF5QixDQUFDO0FBRXZELE9BQU8sRUFBRSxZQUFZLEVBQUUsTUFBTSxFQUFFLEVBQUUsRUFBRSxFQUFFLEVBQUUsSUFBSSxFQUFFLFdBQVcsRUFBRSxDQUFDIn0=
package/package.json CHANGED
@@ -1,16 +1,11 @@
1
1
  {
2
2
  "name": "@push.rocks/smartsecret",
3
- "version": "1.0.1",
3
+ "version": "1.1.0",
4
4
  "private": false,
5
5
  "description": "OS keychain-based secret storage with encrypted-file fallback for Node.js.",
6
6
  "main": "dist_ts/index.js",
7
7
  "typings": "dist_ts/index.d.ts",
8
8
  "type": "module",
9
- "scripts": {
10
- "test": "(tstest test/ --verbose)",
11
- "build": "(tsbuild)",
12
- "buildDocs": "tsdoc"
13
- },
14
9
  "repository": {
15
10
  "type": "git",
16
11
  "url": "https://code.foss.global/push.rocks/smartsecret.git"
@@ -34,11 +29,10 @@
34
29
  },
35
30
  "homepage": "https://code.foss.global/push.rocks/smartsecret",
36
31
  "devDependencies": {
37
- "@git.zone/tsbuild": "^4.1.2",
38
- "@git.zone/tsrun": "^2.0.1",
39
- "@git.zone/tstest": "^3.1.8",
40
- "@push.rocks/tapbundle": "^6.0.3",
41
- "@types/node": "^22.15.0"
32
+ "@git.zone/tsbuild": "^4.4.2",
33
+ "@git.zone/tsrun": "^2.0.6",
34
+ "@git.zone/tstest": "^3.6.7",
35
+ "@types/node": "^22.20.1"
42
36
  },
43
37
  "files": [
44
38
  "ts/**/*",
@@ -49,10 +43,18 @@
49
43
  "dist_ts_web/**/*",
50
44
  "assets/**/*",
51
45
  "cli.js",
52
- "npmextra.json",
46
+ ".smartconfig.json",
53
47
  "readme.md"
54
48
  ],
55
49
  "browserslist": [
56
50
  "last 1 chrome versions"
57
- ]
58
- }
51
+ ],
52
+ "dependencies": {
53
+ "@push.rocks/smartcrypto": "^2.2.0"
54
+ },
55
+ "scripts": {
56
+ "test": "(tstest test/ --verbose)",
57
+ "build": "(tsbuild)",
58
+ "buildDocs": "tsdoc"
59
+ }
60
+ }
package/readme.hints.md CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  ## Architecture
4
4
  - 3-tier backend: macOS Keychain → Linux secret-tool → encrypted file vault
5
- - Zero runtime dependencies (Node.js built-ins only)
5
+ - The legacy SmartSecret backends use Node.js built-ins only
6
6
  - All OS interactions via child_process.execFile (no shell injection)
7
7
 
8
8
  ## File Backend
@@ -10,3 +10,10 @@
10
10
  - Keyfile: auto-generated 32 random bytes at ~/.config/smartsecret/.keyfile (mode 0600)
11
11
  - Key derivation: PBKDF2 (SHA-512, 100k iterations, service-name salt)
12
12
  - Atomic writes: write .tmp then rename()
13
+
14
+ ## SmartSecret Keyring
15
+ - Separate Linux-only API; it does not alter legacy SmartSecret backend selection or behavior
16
+ - Uses @push.rocks/smartcrypto for AES-GCM payload encryption and DEK wrap/unwrap/rewrap
17
+ - Loads exact 32-byte KEKs from validated absolute credential paths into immutable per-instance snapshots
18
+ - Consumers own envelope persistence and compare-and-swap coordination
19
+ - JavaScript key and plaintext zeroization is best effort
package/readme.md CHANGED
@@ -2,6 +2,10 @@
2
2
 
3
3
  OS keychain-based secret storage with encrypted-file fallback for Node.js.
4
4
 
5
+ ## Issue Reporting and Security
6
+
7
+ For reporting bugs, issues, or security vulnerabilities, please visit [community.foss.global/](https://community.foss.global/). This is the central community hub for all issue reporting. Developers who sign and comply with our contribution agreement and go through identification can also get a [code.foss.global/](https://code.foss.global/) account to submit Pull Requests directly.
8
+
5
9
  ## Install
6
10
 
7
11
  To install `@push.rocks/smartsecret`, use pnpm:
@@ -23,7 +27,7 @@ import { SmartSecret } from '@push.rocks/smartsecret';
23
27
  const secretStore = new SmartSecret();
24
28
 
25
29
  // Or specify a custom service name and vault path
26
- const secretStore = new SmartSecret({
30
+ const customSecretStore = new SmartSecret({
27
31
  service: 'my-application',
28
32
  vaultPath: '/path/to/custom/vault.json',
29
33
  });
@@ -98,6 +102,76 @@ const appToken = await appSecrets.getSecret('token'); // 'app-token-value'
98
102
  const ciToken = await ciSecrets.getSecret('token'); // 'ci-token-value'
99
103
  ```
100
104
 
105
+ ## SmartSecret Keyring
106
+
107
+ `SmartSecretKeyring` is a separate Node.js API for DEK/KEK envelopes. It does not change `SmartSecret` or any backend API. The keyring is intentionally Linux-only and fails closed on other platforms.
108
+
109
+ The consumer supplies an ordered key configuration containing exactly one active KEK. Active and retired KEKs are read from absolute credential paths. Each credential must be a regular, non-symlinked, exact 32-byte file without group or world write permissions. Every path component is checked for symlinks, and the loaded key must match its configured SHA-256 fingerprint. Revoked descriptors never contain credential paths or fingerprints.
110
+
111
+ ```typescript
112
+ import { SmartSecretKeyring } from '@push.rocks/smartsecret';
113
+
114
+ const keyring = await SmartSecretKeyring.create({
115
+ keyringId: 'billing.production',
116
+ keys: [
117
+ {
118
+ version: 1,
119
+ status: 'retired',
120
+ credentialPath: '/run/credentials/billing-kek-v1',
121
+ expectedFingerprint: 'sha256:0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef',
122
+ createdAt: 1_700_000_000_000,
123
+ activatedAt: 1_700_000_100_000,
124
+ retiredAt: 1_710_000_000_000,
125
+ escrowReference: 'escrow.billing-v1',
126
+ escrowedAt: 1_700_000_050_000,
127
+ recoveryTestedAt: 1_700_000_075_000,
128
+ },
129
+ {
130
+ version: 2,
131
+ status: 'active',
132
+ credentialPath: '/run/credentials/billing-kek-v2',
133
+ expectedFingerprint: 'sha256:abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789',
134
+ createdAt: 1_709_000_000_000,
135
+ activatedAt: 1_710_000_000_000,
136
+ },
137
+ ],
138
+ });
139
+
140
+ const context = new TextEncoder().encode('tenant:acme/secret:database-password');
141
+ const plaintext = new TextEncoder().encode('secret value');
142
+
143
+ const envelope = await keyring.encryptEnvelope({
144
+ envelopeId: 'secret.database-password',
145
+ plaintext,
146
+ context,
147
+ });
148
+
149
+ const decrypted = await keyring.decryptEnvelope({
150
+ envelope,
151
+ expectedEnvelopeId: 'secret.database-password',
152
+ context,
153
+ });
154
+
155
+ const rewrapped = await keyring.rewrapEnvelope({
156
+ envelope,
157
+ expectedEnvelopeId: 'secret.database-password',
158
+ context,
159
+ targetVersion: 2,
160
+ });
161
+
162
+ const recoveryResult = await keyring.recoverySelfTest(2);
163
+ const inspection = keyring.inspect();
164
+ keyring.destroy();
165
+ ```
166
+
167
+ `encryptEnvelope()` always uses the sole active KEK. `decryptEnvelope()` permits active and retired KEKs, but rejects revoked and unknown versions. `rewrapEnvelope()` authenticates the current wrapper and context, then calls SmartCrypto's DEK rewrap operation without decrypting the payload. It preserves the payload nonce, ciphertext, and tag exactly.
168
+
169
+ The profile is fixed to `smartsecret-aes-256-gcm-dek-kek-v1`. Payloads may contain 0 through 524,288 bytes. Context is mandatory and may contain 1 through 65,536 bytes. Identifiers use the pattern `^[A-Za-z0-9][A-Za-z0-9:._-]{0,199}$` and are limited to 200 UTF-8 bytes.
170
+
171
+ Key loading is an immutable per-instance snapshot. Replacing a credential file does not reload an existing keyring; create a new instance and swap it at the application boundary. `destroy()` marks an instance unusable and best-effort wipes retained key arrays. Operation-owned key, plaintext, and context copies are also wiped in `finally` blocks. JavaScript runtimes, garbage collectors, and native cryptographic implementations can retain copies outside package control, so zeroization is best effort rather than a hard memory-erasure guarantee.
172
+
173
+ The package never persists envelopes. The consumer owns durable envelope storage, compare-and-swap coordination during rewrap, and any metadata updates after recovery testing.
174
+
101
175
  ## API Reference
102
176
 
103
177
  ### `SmartSecret`
@@ -125,6 +199,25 @@ new SmartSecret(options?: ISmartSecretOptions)
125
199
  | `listAccounts` | `() => Promise<string[]>` | List all account names for the configured service |
126
200
  | `getBackendType` | `() => Promise<TBackendType>` | Returns the active backend identifier |
127
201
 
202
+ ### `SmartSecretKeyring`
203
+
204
+ Create keyrings through the asynchronous factory. The constructor is not public.
205
+
206
+ ```typescript
207
+ SmartSecretKeyring.create(config: ISmartSecretKeyringConfig): Promise<SmartSecretKeyring>
208
+ ```
209
+
210
+ | Method | Signature | Description |
211
+ | --- | --- | --- |
212
+ | `encryptEnvelope` | `(options: IEncryptSmartSecretEnvelopeOptions) => Promise<ISmartSecretEnvelopeV1>` | Encrypt with a random DEK and wrap it under the active KEK |
213
+ | `decryptEnvelope` | `(options: IDecryptSmartSecretEnvelopeOptions) => Promise<Uint8Array>` | Strictly parse, authenticate, and decrypt an envelope |
214
+ | `rewrapEnvelope` | `(options: IRewrapSmartSecretEnvelopeOptions) => Promise<ISmartSecretEnvelopeV1>` | Rewrap only the DEK under the active KEK |
215
+ | `recoverySelfTest` | `(version: number) => Promise<ISmartSecretRecoverySelfTestResult>` | Wrap and unwrap a synthetic random DEK for an active or retired version |
216
+ | `inspect` | `() => ISmartSecretKeyringInspection` | Return cloned, value-free metadata without credential paths or key bytes |
217
+ | `destroy` | `() => void` | Mark the instance unusable and best-effort wipe retained key arrays |
218
+
219
+ All keyring failures are `SmartSecretKeyringError` instances with a stable `TSmartSecretKeyringErrorCode`. Their message, JSON representation, Node.js inspection output, and stack contain only the code and fixed package boilerplate. Filesystem and SmartCrypto causes are not retained.
220
+
128
221
  ### Types
129
222
 
130
223
  ```typescript
@@ -188,6 +281,21 @@ Both paths can be influenced by providing a custom `vaultPath` in the constructo
188
281
 
189
282
  ## License and Legal Information
190
283
 
191
- This project is licensed under the MIT license. For more details, see the `license` file in the repository.
284
+ This repository contains open-source code licensed under the MIT License. A copy of the license can be found in the repository license file.
285
+
286
+ **Please note:** The MIT License does not grant permission to use the trade names, trademarks, service marks, or product names of the project, except as required for reasonable and customary use in describing the origin of the work and reproducing the content of the NOTICE file.
287
+
288
+ ### Trademarks
289
+
290
+ This project is owned and maintained by Task Venture Capital GmbH. The names and logos associated with Task Venture Capital GmbH and any related products or services are trademarks of Task Venture Capital GmbH or third parties, and are not included within the scope of the MIT license granted herein.
291
+
292
+ Use of these trademarks must comply with Task Venture Capital GmbH's Trademark Guidelines or the guidelines of the respective third-party owners, and any usage must be approved in writing. Third-party trademarks used herein are the property of their respective owners and used only in a descriptive manner, e.g. for an implementation of an API or similar.
293
+
294
+ ### Company Information
295
+
296
+ Task Venture Capital GmbH<br>
297
+ Registered at District Court Bremen HRB 35230 HB, Germany
298
+
299
+ For any legal inquiries or further information, please contact us via email at hello@task.vc.
192
300
 
193
- By using this package, you agree to the licensing terms.
301
+ By using this repository, you acknowledge that you have read this section, agree to comply with its terms, and understand that the licensing of the code does not imply endorsement by Task Venture Capital GmbH of any derivative works.
@@ -3,6 +3,6 @@
3
3
  */
4
4
  export const commitinfo = {
5
5
  name: '@push.rocks/smartsecret',
6
- version: '1.0.1',
6
+ version: '1.1.0',
7
7
  description: 'OS keychain-based secret storage with encrypted-file fallback for Node.js.'
8
8
  }
package/ts/index.ts CHANGED
@@ -3,3 +3,13 @@ export type { ISecretBackend, TBackendType } from './smartsecret.backends.base.j
3
3
  export { MacosKeychainBackend } from './smartsecret.backends.macos.js';
4
4
  export { LinuxSecretServiceBackend } from './smartsecret.backends.linux.js';
5
5
  export { FileEncryptedBackend } from './smartsecret.backends.file.js';
6
+ export * from './smartsecret.keyring.js';
7
+ export { SmartSecretKeyringError } from './smartsecret.keyring.error.js';
8
+ export type {
9
+ ISmartSecretKeyringErrorJson,
10
+ TSmartSecretKeyringErrorCode,
11
+ } from './smartsecret.keyring.error.js';
12
+ export type {
13
+ ISmartSecretEnvelopeV1,
14
+ TSmartSecretKeyringProfile,
15
+ } from './smartsecret.keyring.protocol.js';