@bedrock/record-cipher 1.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.
- package/LICENSE.md +115 -0
- package/README.md +3 -0
- package/lib/RecordCipher.js +309 -0
- package/lib/index.js +4 -0
- package/package.json +45 -0
package/LICENSE.md
ADDED
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
Bedrock Non-Commercial License v1.0
|
|
2
|
+
===================================
|
|
3
|
+
|
|
4
|
+
Copyright (c) 2011-2016 Digital Bazaar, Inc.
|
|
5
|
+
All rights reserved.
|
|
6
|
+
|
|
7
|
+
Summary
|
|
8
|
+
=======
|
|
9
|
+
|
|
10
|
+
This license allows the licensee to use Bedrock and its software modules
|
|
11
|
+
for non-commercial purposes such as self-study, research, personal
|
|
12
|
+
projects, or for evaluation purposes. If the licensee uses Bedrock
|
|
13
|
+
directly or indirectly to generate revenue, or to provide products or
|
|
14
|
+
services to more than 500 people (users), the licensee must immediately
|
|
15
|
+
obtain a non-profit or commercial license.
|
|
16
|
+
|
|
17
|
+
Examples
|
|
18
|
+
========
|
|
19
|
+
|
|
20
|
+
These are examples of cases that are allowed by this license:
|
|
21
|
+
|
|
22
|
+
* The licensee is an individual that creates Bedrock-dependent software for
|
|
23
|
+
personal use only.
|
|
24
|
+
* The licensee is an individual or group of students/researchers that uses
|
|
25
|
+
Bedrock to experiment with an idea for a non-commercial project.
|
|
26
|
+
* The licensee is a startup company that prototypes a Bedrock-dependent
|
|
27
|
+
product before they have cash flow and will be testing the prototype
|
|
28
|
+
software with less than 500 users. The service will not generate revenue
|
|
29
|
+
of any kind.
|
|
30
|
+
* The licensee is a for-profit organization that creates a product or
|
|
31
|
+
service that is used by less than 500 users and is built with or
|
|
32
|
+
integrates with Bedrock. The service must be exclusively provided for free
|
|
33
|
+
and no parent, subsidiary, agent, or affiliate organization may profit
|
|
34
|
+
from its use.
|
|
35
|
+
|
|
36
|
+
These cases require a non-profit or commercial license:
|
|
37
|
+
|
|
38
|
+
* The licensee is a non-profit that receives funding to create and/or run a
|
|
39
|
+
Bedrock-dependent service.
|
|
40
|
+
* The licensee is a startup company with Bedrock-dependent software that is
|
|
41
|
+
funded by another organization.
|
|
42
|
+
* The licensee is a startup company that is going into production with
|
|
43
|
+
Bedrock-dependent software.
|
|
44
|
+
* The licensee has more than 500 users using a Bedrock-dependent service
|
|
45
|
+
either directly or indirectly.
|
|
46
|
+
* The licensee is a medium to large organization that builds or integrates a
|
|
47
|
+
commercial product or service with Bedrock.
|
|
48
|
+
|
|
49
|
+
THE LICENSE
|
|
50
|
+
===========
|
|
51
|
+
|
|
52
|
+
This section and all subsequent sections of this document constitute the
|
|
53
|
+
agreement between the licensee and Digital Bazaar, Inc.
|
|
54
|
+
|
|
55
|
+
DEFINITIONS
|
|
56
|
+
===========
|
|
57
|
+
|
|
58
|
+
* Product - The Bedrock software and any modules associated with Bedrock
|
|
59
|
+
where Digital Bazaar, Inc. owns the copyright.
|
|
60
|
+
|
|
61
|
+
CONDITIONS
|
|
62
|
+
==========
|
|
63
|
+
|
|
64
|
+
Redistribution and use in source and binary forms, with or without
|
|
65
|
+
modification, are permitted for NON-COMMERCIAL PURPOSES as long as the
|
|
66
|
+
following conditions are met:
|
|
67
|
+
|
|
68
|
+
1. Any use of the Product must not generate revenue for the licensee or
|
|
69
|
+
any parent, subsidiary, agent, or affiliate of the licensee. Use of
|
|
70
|
+
Product includes, but is not limited to, interacting with any of the
|
|
71
|
+
licensee's Product-dependent products or services over a network.
|
|
72
|
+
|
|
73
|
+
2. The aggregate number of individual people (users) of the licensee's
|
|
74
|
+
products or services that use Product must be less than 500.
|
|
75
|
+
|
|
76
|
+
3. Redistributions of source code must retain the above copyright notice
|
|
77
|
+
intact, this list of conditions and the following disclaimer.
|
|
78
|
+
|
|
79
|
+
4. Redistributions in binary form must reproduce the above copyright
|
|
80
|
+
notice, this license and the following disclaimer in the documentation and
|
|
81
|
+
on a web page available via interactive use and/or other materials
|
|
82
|
+
provided with the distribution.
|
|
83
|
+
|
|
84
|
+
5. Neither the name of the copyright holder, the names of its
|
|
85
|
+
contributors, nor any trademarks held by the copyright holder may be used
|
|
86
|
+
to endorse or promote products or services built using the Product without
|
|
87
|
+
specific prior written permission.
|
|
88
|
+
|
|
89
|
+
6. Any modifications are clearly outlined in release documentation and are
|
|
90
|
+
specifically mentioned as not being a part of an official Product release.
|
|
91
|
+
No additional restrictions to this license may be made when distributing
|
|
92
|
+
modifications.
|
|
93
|
+
|
|
94
|
+
7. For the avoidance of doubt, this license prohibits sublicensing of the
|
|
95
|
+
Product.
|
|
96
|
+
|
|
97
|
+
8. Any breach of this license by licensee must be resolved within 30 days.
|
|
98
|
+
Failure to do so results in the termination of this license.
|
|
99
|
+
|
|
100
|
+
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
|
|
101
|
+
IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
|
|
102
|
+
THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
|
|
103
|
+
PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
|
|
104
|
+
CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
|
|
105
|
+
EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
|
106
|
+
PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
|
|
107
|
+
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
|
|
108
|
+
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|
|
109
|
+
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
|
110
|
+
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
111
|
+
|
|
112
|
+
To obtain a non-profit or commercial license for Product, please contact
|
|
113
|
+
Digital Bazaar, Inc. at the following email address:
|
|
114
|
+
|
|
115
|
+
Digital Bazaar <support@digitalbazaar.com>
|
package/README.md
ADDED
|
@@ -0,0 +1,309 @@
|
|
|
1
|
+
/*!
|
|
2
|
+
* Copyright (c) 2019-2026 Digital Bazaar, Inc.
|
|
3
|
+
*/
|
|
4
|
+
import * as bedrock from '@bedrock/core';
|
|
5
|
+
import {decode, encode} from 'cborg';
|
|
6
|
+
import {generalDecrypt, GeneralEncrypt} from 'jose';
|
|
7
|
+
|
|
8
|
+
const {util: {BedrockError}} = bedrock;
|
|
9
|
+
|
|
10
|
+
const TEXT_ENCODER = new TextEncoder();
|
|
11
|
+
const TEXT_DECODER = new TextDecoder();
|
|
12
|
+
|
|
13
|
+
/* Multikey registry IDs and encoded header values
|
|
14
|
+
aes-256 | 0xa2 | 256-bit AES symmetric key
|
|
15
|
+
*/
|
|
16
|
+
const SUPPORTED_KEK_TYPES = new Map([
|
|
17
|
+
['aes-256', {header: new Uint8Array([0xa2, 0x01]), size: 32}]
|
|
18
|
+
]);
|
|
19
|
+
|
|
20
|
+
export class RecordCipher {
|
|
21
|
+
constructor({currentKekId, keks, encoding} = {}) {
|
|
22
|
+
this.keks = keks;
|
|
23
|
+
this.encoding = encoding;
|
|
24
|
+
this.setCurrentKek({id: currentKekId});
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Decrypts `encryptedSecrets`, if found, in a record.
|
|
29
|
+
*
|
|
30
|
+
* @param {object} options - The options to use.
|
|
31
|
+
* @param {object} options.record - The record with optional
|
|
32
|
+
* `encryptedSecrets` to decrypt.
|
|
33
|
+
*
|
|
34
|
+
* @returns {Promise<object>} An object with `secrets` instead of
|
|
35
|
+
* `encryptedSecrets`.
|
|
36
|
+
*/
|
|
37
|
+
async decryptRecordSecrets({record} = {}) {
|
|
38
|
+
if(record.encryptedSecrets === undefined) {
|
|
39
|
+
// nothing to decrypt, return early
|
|
40
|
+
return record;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
try {
|
|
44
|
+
// decrypt secrets
|
|
45
|
+
const {encryptedSecrets, ...rest} = record;
|
|
46
|
+
const {kekId, jwe, encoding = this.encoding} = encryptedSecrets;
|
|
47
|
+
const secretKey = await this.getKek({id: kekId});
|
|
48
|
+
const {plaintext} = await generalDecrypt(jwe, secretKey);
|
|
49
|
+
const secrets = encoding === 'cbor' ?
|
|
50
|
+
_cborDecodeSecrets(plaintext) : _jsonDecodeSecrets(plaintext);
|
|
51
|
+
|
|
52
|
+
// new record object w/decrypted secrets
|
|
53
|
+
return {...rest, secrets};
|
|
54
|
+
} catch(cause) {
|
|
55
|
+
throw new BedrockError('Could not decrypt record secrets.', {
|
|
56
|
+
name: 'OperationError',
|
|
57
|
+
cause,
|
|
58
|
+
details: {
|
|
59
|
+
public: true,
|
|
60
|
+
httpStatusCode: 500
|
|
61
|
+
}
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Encrypts `secrets`, if found, in a record.
|
|
68
|
+
*
|
|
69
|
+
* @param {object} options - The options to use.
|
|
70
|
+
* @param {object} options.record - The record with optional `secrets` to
|
|
71
|
+
* encrypt.
|
|
72
|
+
*
|
|
73
|
+
* @returns {Promise<object>} An object with `encryptedSecrets` instead of
|
|
74
|
+
* `secrets`.
|
|
75
|
+
*/
|
|
76
|
+
async encryptRecordSecrets({record} = {}) {
|
|
77
|
+
if(record.encryptedSecrets !== undefined) {
|
|
78
|
+
// should not happen; bad call
|
|
79
|
+
throw new Error(
|
|
80
|
+
'Could not encrypt record secrets; secrets already encrypted.');
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
try {
|
|
84
|
+
// get current wrap key ID
|
|
85
|
+
const {currentKekId: kekId, encoding} = this;
|
|
86
|
+
if(!kekId) {
|
|
87
|
+
// no KEK config; return early
|
|
88
|
+
return record;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// encrypt secrets
|
|
92
|
+
const {secrets, ...nonSecrets} = record;
|
|
93
|
+
const plaintext = encoding === 'cbor' ?
|
|
94
|
+
_cborEncodeSecrets(secrets) : _jsonEncodeSecrets(secrets);
|
|
95
|
+
const secretKey = await this.getKek({id: kekId});
|
|
96
|
+
const jwe = await new GeneralEncrypt(plaintext)
|
|
97
|
+
.setProtectedHeader({enc: 'A256GCM'})
|
|
98
|
+
.addRecipient(secretKey)
|
|
99
|
+
.setUnprotectedHeader({alg: 'A256KW', kid: kekId})
|
|
100
|
+
.encrypt();
|
|
101
|
+
|
|
102
|
+
// return new record w/encrypted secrets
|
|
103
|
+
return {
|
|
104
|
+
...nonSecrets,
|
|
105
|
+
encryptedSecrets: {kekId, jwe, encoding}
|
|
106
|
+
};
|
|
107
|
+
} catch(cause) {
|
|
108
|
+
throw new BedrockError('Could not encrypt record secrets.', {
|
|
109
|
+
name: 'OperationError',
|
|
110
|
+
cause,
|
|
111
|
+
details: {
|
|
112
|
+
public: true,
|
|
113
|
+
httpStatusCode: 500
|
|
114
|
+
}
|
|
115
|
+
});
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
addKek({id, secretKey, secretKeyMultibase} = {}) {
|
|
120
|
+
if(this.keks.has(id)) {
|
|
121
|
+
throw new BedrockError(`Key encryption key "${id}" already set.`, {
|
|
122
|
+
name: 'ConstraintsError',
|
|
123
|
+
details: {
|
|
124
|
+
public: true,
|
|
125
|
+
httpStatusCode: 409
|
|
126
|
+
}
|
|
127
|
+
});
|
|
128
|
+
}
|
|
129
|
+
if(secretKey) {
|
|
130
|
+
this.keks.set(id, secretKey);
|
|
131
|
+
} else {
|
|
132
|
+
this.keks.set(id, _loadKek(secretKeyMultibase));
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
async getKek({id} = {}) {
|
|
137
|
+
const secretKey = this.keks.get(id);
|
|
138
|
+
if(secretKey) {
|
|
139
|
+
return secretKey;
|
|
140
|
+
}
|
|
141
|
+
throw new BedrockError(`Key encryption key "${id}" not found.`, {
|
|
142
|
+
name: 'NotFoundError',
|
|
143
|
+
details: {
|
|
144
|
+
public: true,
|
|
145
|
+
httpStatusCode: 400
|
|
146
|
+
}
|
|
147
|
+
});
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
isSecretsEncryptionEnabled() {
|
|
151
|
+
return this.currentKekId !== null;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
setCurrentKek({id} = {}) {
|
|
155
|
+
if(id !== null) {
|
|
156
|
+
// ensure secret key exists
|
|
157
|
+
if(!this.keks.has(id)) {
|
|
158
|
+
throw new BedrockError(`Key encryption key "${id}" not found.`, {
|
|
159
|
+
name: 'NotFoundError',
|
|
160
|
+
details: {
|
|
161
|
+
public: true,
|
|
162
|
+
httpStatusCode: 400
|
|
163
|
+
}
|
|
164
|
+
});
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
this.currentKekId = id;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
/**
|
|
171
|
+
* Creates a `RecordCipher` instance for encrypting and/or decrypting
|
|
172
|
+
* record `secrets`. The default encoding mode for the `secrets` is `cbor`,
|
|
173
|
+
* which supports any binary subfields present in `secrets`. The encoding
|
|
174
|
+
* can alternatively be set to `json` for backwards compatiblity with
|
|
175
|
+
* modules that previously encoded using JSON, not CBOR.
|
|
176
|
+
*
|
|
177
|
+
* @param {object} options - The options to use.
|
|
178
|
+
* @param {string} [options.currentKekId] - The ID of the KEK to use for
|
|
179
|
+
* new encryptions; may be set to `null` to disable encryption; if not
|
|
180
|
+
* set, it will be automatically set to the first KEK ID in the `keks`
|
|
181
|
+
* array if it is not empty (otherwise it will be set to `null`).
|
|
182
|
+
* @param {Array} [options.keks=[]] - An array of objects, each with an `id`
|
|
183
|
+
* `id` value for a KEK to use for encryption and decryption as well as its
|
|
184
|
+
* `secretKeyMultibase` including a base64url-encoded AES-256 key value.
|
|
185
|
+
* @param {string} [options.encoding='cbor'] - The encoding to use for the
|
|
186
|
+
* values found in `secrets`; either `cbor` or `json`, `cbor` supports
|
|
187
|
+
* binary values and `json` does not and is only provided for backwards
|
|
188
|
+
* compatibility and should not be used with new applications).
|
|
189
|
+
*
|
|
190
|
+
* @returns {Promise<RecordCipher>} A new RecordCipher instance based on
|
|
191
|
+
* the given options; if `currentKekId` is specified as `null` then this
|
|
192
|
+
* instance will return `false` from `isSecretsEncryptionEnabled()`.
|
|
193
|
+
*/
|
|
194
|
+
static async create({currentKekId, keks = [], encoding = 'cbor'} = {}) {
|
|
195
|
+
if(!(currentKekId === undefined || currentKekId === null ||
|
|
196
|
+
typeof currentKekId !== 'string')) {
|
|
197
|
+
throw new TypeError('"currentKekId" must be a string or null.');
|
|
198
|
+
}
|
|
199
|
+
if(!Array.isArray(keks)) {
|
|
200
|
+
throw new TypeError('"keks" must be an array.');
|
|
201
|
+
}
|
|
202
|
+
if(!(encoding === 'cbor' || encoding === 'json')) {
|
|
203
|
+
throw new Error('"encoding" must be "cbor" or "json".');
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
const recordCipher = new RecordCipher({
|
|
207
|
+
currentKekId: null, keks: new Map(), encoding
|
|
208
|
+
});
|
|
209
|
+
|
|
210
|
+
// add all KEKs in `keks`
|
|
211
|
+
let firstId;
|
|
212
|
+
for(const key of keks) {
|
|
213
|
+
if(!(key.id && typeof key.id === 'string')) {
|
|
214
|
+
throw new BedrockError(
|
|
215
|
+
'Invalid key encryption key configuration; ' +
|
|
216
|
+
'key "id" must be a string.', {
|
|
217
|
+
name: 'DataError',
|
|
218
|
+
details: {
|
|
219
|
+
public: true,
|
|
220
|
+
httpStatusCode: 400
|
|
221
|
+
}
|
|
222
|
+
});
|
|
223
|
+
}
|
|
224
|
+
if(firstId === undefined) {
|
|
225
|
+
firstId = key.id;
|
|
226
|
+
}
|
|
227
|
+
recordCipher.addKek(key);
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
if(currentKekId !== null) {
|
|
231
|
+
recordCipher.setCurrentKek({id: currentKekId ?? firstId ?? null});
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
return recordCipher;
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
function _cborDecodeSecrets(plaintext) {
|
|
239
|
+
const decoded = decode(plaintext);
|
|
240
|
+
// convert Uint8Arrays to Buffers for compatibility
|
|
241
|
+
for(const [key, value] of Object.entries(decoded)) {
|
|
242
|
+
decoded[key] = value instanceof Uint8Array ? Buffer.from(value) : value;
|
|
243
|
+
}
|
|
244
|
+
return decoded;
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
function _cborEncodeSecrets(secrets) {
|
|
248
|
+
return encode(secrets);
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
function _jsonDecodeSecrets(plaintext) {
|
|
252
|
+
return JSON.parse(TEXT_DECODER.decode(plaintext));
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
function _jsonEncodeSecrets(secrets) {
|
|
256
|
+
const obj = secrets instanceof Map ?
|
|
257
|
+
Object.fromEntries(secrets.entries()) : secrets;
|
|
258
|
+
return TEXT_ENCODER.encode(JSON.stringify(obj));
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
function _loadKek(secretKeyMultibase) {
|
|
262
|
+
if(!secretKeyMultibase?.startsWith('u')) {
|
|
263
|
+
throw new BedrockError(
|
|
264
|
+
'Unsupported multibase header; ' +
|
|
265
|
+
'"u" for base64url-encoding must be used.', {
|
|
266
|
+
name: 'NotSupportedError',
|
|
267
|
+
details: {
|
|
268
|
+
public: true,
|
|
269
|
+
httpStatusCode: 400
|
|
270
|
+
}
|
|
271
|
+
});
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
// check multikey header
|
|
275
|
+
let keyType;
|
|
276
|
+
let secretKey;
|
|
277
|
+
const multikey = Buffer.from(secretKeyMultibase.slice(1), 'base64url');
|
|
278
|
+
for(const [type, {header, size}] of SUPPORTED_KEK_TYPES) {
|
|
279
|
+
if(multikey[0] === header[0] && multikey[1] === header[1]) {
|
|
280
|
+
keyType = type;
|
|
281
|
+
if(multikey.length !== (2 + size)) {
|
|
282
|
+
// intentionally do not report what was detected because a
|
|
283
|
+
// misconfigured secret could have its first two bytes revealed
|
|
284
|
+
throw new BedrockError(
|
|
285
|
+
'Incorrect multikey size or invalid multikey header.', {
|
|
286
|
+
name: 'DataError',
|
|
287
|
+
details: {
|
|
288
|
+
public: true,
|
|
289
|
+
httpStatusCode: 400
|
|
290
|
+
}
|
|
291
|
+
});
|
|
292
|
+
}
|
|
293
|
+
secretKey = multikey.subarray(2);
|
|
294
|
+
break;
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
if(keyType === undefined) {
|
|
298
|
+
throw new BedrockError(
|
|
299
|
+
'Unsupported multikey type; only AES-256 is supported.', {
|
|
300
|
+
name: 'NotSupportedError',
|
|
301
|
+
details: {
|
|
302
|
+
public: true,
|
|
303
|
+
httpStatusCode: 400
|
|
304
|
+
}
|
|
305
|
+
});
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
return secretKey;
|
|
309
|
+
}
|
package/lib/index.js
ADDED
package/package.json
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@bedrock/record-cipher",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"description": "Bedrock Record Cipher",
|
|
6
|
+
"license": "SEE LICENSE IN LICENSE.md",
|
|
7
|
+
"main": "./lib/index.js",
|
|
8
|
+
"files": [
|
|
9
|
+
"lib/**/*.js"
|
|
10
|
+
],
|
|
11
|
+
"scripts": {
|
|
12
|
+
"lint": "eslint ."
|
|
13
|
+
},
|
|
14
|
+
"repository": {
|
|
15
|
+
"type": "git",
|
|
16
|
+
"url": "https://github.com/digitalbazaar/bedrock-record-cipher"
|
|
17
|
+
},
|
|
18
|
+
"author": {
|
|
19
|
+
"name": "Digital Bazaar, Inc.",
|
|
20
|
+
"email": "support@digitalbazaar.com",
|
|
21
|
+
"url": "https://digitalbazaar.com"
|
|
22
|
+
},
|
|
23
|
+
"bugs": {
|
|
24
|
+
"url": "https://github.com/digitalbazaar/bedrock-record-cipher/issues"
|
|
25
|
+
},
|
|
26
|
+
"homepage": "https://github.com/digitalbazaar/bedrock-record-cipher",
|
|
27
|
+
"dependencies": {
|
|
28
|
+
"cborg": "^5.0.1",
|
|
29
|
+
"jose": "^6.2.2"
|
|
30
|
+
},
|
|
31
|
+
"peerDependencies": {
|
|
32
|
+
"@bedrock/core": "^6.3.0"
|
|
33
|
+
},
|
|
34
|
+
"engines": {
|
|
35
|
+
"node": ">=20"
|
|
36
|
+
},
|
|
37
|
+
"devDependencies": {
|
|
38
|
+
"eslint": "^8.57.1",
|
|
39
|
+
"eslint-config-digitalbazaar": "^5.2.0",
|
|
40
|
+
"eslint-plugin-jsdoc": "^50.6.3",
|
|
41
|
+
"eslint-plugin-unicorn": "^56.0.1",
|
|
42
|
+
"jsdoc": "^4.0.4",
|
|
43
|
+
"jsdoc-to-markdown": "^9.1.1"
|
|
44
|
+
}
|
|
45
|
+
}
|