@keetanetwork/anchor 0.0.13 → 0.0.14
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/lib/certificates.d.ts +83 -21
- package/lib/certificates.d.ts.map +1 -1
- package/lib/certificates.generated.d.ts +3 -0
- package/lib/certificates.generated.d.ts.map +1 -0
- package/lib/certificates.generated.js +120 -0
- package/lib/certificates.generated.js.map +1 -0
- package/lib/certificates.js +579 -117
- package/lib/certificates.js.map +1 -1
- package/lib/http-server.d.ts +2 -1
- package/lib/http-server.d.ts.map +1 -1
- package/lib/http-server.js +4 -4
- package/lib/http-server.js.map +1 -1
- package/lib/resolver.d.ts +2 -1
- package/lib/resolver.d.ts.map +1 -1
- package/lib/resolver.js.map +1 -1
- package/lib/utils/asn1.d.ts +2 -1
- package/lib/utils/asn1.d.ts.map +1 -1
- package/lib/utils/asn1.js.map +1 -1
- package/lib/utils/buffer.d.ts +3 -0
- package/lib/utils/buffer.d.ts.map +1 -1
- package/lib/utils/buffer.js +26 -0
- package/lib/utils/buffer.js.map +1 -1
- package/lib/utils/guards.d.ts +14 -0
- package/lib/utils/guards.d.ts.map +1 -0
- package/lib/utils/guards.js +31 -0
- package/lib/utils/guards.js.map +1 -0
- package/lib/utils/index.d.ts +3 -1
- package/lib/utils/index.d.ts.map +1 -1
- package/lib/utils/index.js +3 -1
- package/lib/utils/index.js.map +1 -1
- package/lib/utils/oid.d.ts +7 -0
- package/lib/utils/oid.d.ts.map +1 -0
- package/lib/utils/oid.js +22 -0
- package/lib/utils/oid.js.map +1 -0
- package/lib/utils/signing.d.ts.map +1 -1
- package/lib/utils/signing.js +26 -2
- package/lib/utils/signing.js.map +1 -1
- package/npm-shrinkwrap.json +2 -2
- package/package.json +1 -1
- package/services/fx/server.js +1 -1
- package/services/fx/server.js.map +1 -1
- package/services/kyc/client.d.ts.map +1 -1
- package/services/kyc/client.js +1 -8
- package/services/kyc/client.js.map +1 -1
- package/services/kyc/common.d.ts +39 -3
- package/services/kyc/common.d.ts.map +1 -1
- package/services/kyc/common.generated.d.ts +5 -0
- package/services/kyc/common.generated.d.ts.map +1 -0
- package/services/kyc/common.generated.js +241 -0
- package/services/kyc/common.generated.js.map +1 -0
- package/services/kyc/common.js +68 -1
- package/services/kyc/common.js.map +1 -1
- package/services/kyc/iso20022.generated.d.ts +285 -0
- package/services/kyc/iso20022.generated.d.ts.map +1 -0
- package/services/kyc/iso20022.generated.js +205 -0
- package/services/kyc/iso20022.generated.js.map +1 -0
- package/services/kyc/oids.generated.d.ts +83 -0
- package/services/kyc/oids.generated.d.ts.map +1 -0
- package/services/kyc/oids.generated.js +130 -0
- package/services/kyc/oids.generated.js.map +1 -0
- package/services/kyc/server.d.ts +141 -0
- package/services/kyc/server.d.ts.map +1 -0
- package/services/kyc/server.js +183 -0
- package/services/kyc/server.js.map +1 -0
- package/services/kyc/utils/generate-kyc-schema.d.ts +3 -0
- package/services/kyc/utils/generate-kyc-schema.d.ts.map +1 -0
- package/services/kyc/utils/generate-kyc-schema.js +1154 -0
- package/services/kyc/utils/generate-kyc-schema.js.map +1 -0
package/lib/certificates.js
CHANGED
|
@@ -1,69 +1,38 @@
|
|
|
1
|
+
import * as util from 'util';
|
|
1
2
|
import * as KeetaNetClient from '@keetanetwork/keetanet-client';
|
|
3
|
+
import * as oids from '../services/kyc/oids.generated.js';
|
|
2
4
|
import * as ASN1 from './utils/asn1.js';
|
|
3
|
-
import {
|
|
5
|
+
import { ASN1toJS } from './utils/asn1.js';
|
|
6
|
+
import { arrayBufferLikeToBuffer, arrayBufferToBuffer, Buffer, bufferToArrayBuffer } from './utils/buffer.js';
|
|
4
7
|
import crypto from './utils/crypto.js';
|
|
5
8
|
import { assertNever } from './utils/never.js';
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
9
|
+
import { CertificateAttributeOIDDB, CertificateAttributeSchema, CertificateAttributeFieldNames } from '../services/kyc/iso20022.generated.js';
|
|
10
|
+
import { hasIndexSignature, isErrorLike, hasValueProp, isContextTagged } from './utils/guards.js';
|
|
11
|
+
import { getOID, lookupByOID } from './utils/oid.js';
|
|
12
|
+
import { convertToJSON as convertToJSONUtil } from './utils/json.js';
|
|
13
|
+
import { EncryptedContainer } from './encrypted-container.js';
|
|
14
|
+
import { assertSharableCertificateAttributesContentsSchema } from './certificates.generated.js';
|
|
15
|
+
/**
|
|
16
|
+
* Short alias for printing a debug representation of an object
|
|
17
|
+
*/
|
|
18
|
+
const DPO = KeetaNetClient.lib.Utils.Helper.debugPrintableObject.bind(KeetaNetClient.lib.Utils.Helper);
|
|
19
|
+
const KeetaNetAccount = KeetaNetClient.lib.Account;
|
|
20
|
+
function toJSON(data) {
|
|
21
|
+
return (convertToJSONUtil(data));
|
|
19
22
|
}
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
if (value === oid) {
|
|
26
|
-
return (key);
|
|
27
|
-
}
|
|
28
|
-
}
|
|
29
|
-
throw (new Error(`Unknown OID: ${oid}`));
|
|
23
|
+
// Generic type guard to align decoded values with generated attribute types
|
|
24
|
+
function isAttributeValue(_name, _v) {
|
|
25
|
+
// Runtime schema validation is already performed by BufferStorageASN1; this guard
|
|
26
|
+
// serves to inform TypeScript of the precise type tied to the attribute name.
|
|
27
|
+
return (true);
|
|
30
28
|
}
|
|
31
|
-
|
|
32
|
-
function
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
if ('publicKeyString' in value && typeof value.publicKeyString === 'object' && value.publicKeyString !== null) {
|
|
38
|
-
if ('get' in value.publicKeyString && typeof value.publicKeyString.get === 'function') {
|
|
39
|
-
/*
|
|
40
|
-
* If the value has a publicKeyString property that is an
|
|
41
|
-
* object with a get method, we assume it is a KeetaNetAccount
|
|
42
|
-
* or similar object and we return the public key string
|
|
43
|
-
*/
|
|
44
|
-
// eslint-disable-next-line @typescript-eslint/no-unsafe-call
|
|
45
|
-
const publicKeyString = value.publicKeyString.get();
|
|
46
|
-
if (typeof publicKeyString === 'string') {
|
|
47
|
-
return (publicKeyString);
|
|
48
|
-
}
|
|
49
|
-
}
|
|
50
|
-
}
|
|
51
|
-
}
|
|
52
|
-
if (Buffer.isBuffer(value)) {
|
|
53
|
-
return (value.toString('base64'));
|
|
54
|
-
}
|
|
55
|
-
if (typeof value === 'bigint') {
|
|
56
|
-
return (value.toString());
|
|
57
|
-
}
|
|
58
|
-
return (convertedValue);
|
|
59
|
-
}));
|
|
60
|
-
return (retval);
|
|
29
|
+
// Helper to apply type guard once and return the properly typed value
|
|
30
|
+
function asAttributeValue(name, v) {
|
|
31
|
+
if (!isAttributeValue(name, v)) {
|
|
32
|
+
throw (new Error('internal error: decoded value did not match expected type'));
|
|
33
|
+
}
|
|
34
|
+
return (v);
|
|
61
35
|
}
|
|
62
|
-
/*
|
|
63
|
-
* Because our public interfaces are ArrayBuffers we often need to convert
|
|
64
|
-
* Buffers to ArrayBuffers -- an alias to the Node function to do that
|
|
65
|
-
*/
|
|
66
|
-
const bufferToArrayBuffer = KeetaNetClient.lib.Utils.Helper.bufferToArrayBuffer.bind(KeetaNetClient.lib.Utils.Helper);
|
|
67
36
|
/**
|
|
68
37
|
* Sensitive Attribute Schema
|
|
69
38
|
*
|
|
@@ -107,30 +76,161 @@ const SensitiveAttributeSchemaInternal = [
|
|
|
107
76
|
* Database of permitted algorithms and their OIDs
|
|
108
77
|
*/
|
|
109
78
|
const sensitiveAttributeOIDDB = {
|
|
110
|
-
'aes-256-gcm':
|
|
111
|
-
'aes-256-cbc':
|
|
112
|
-
'sha2-256':
|
|
113
|
-
'sha3-256':
|
|
79
|
+
'aes-256-gcm': oids.AES_256_GCM,
|
|
80
|
+
'aes-256-cbc': oids.AES_256_CBC,
|
|
81
|
+
'sha2-256': oids.SHA2_256,
|
|
82
|
+
'sha3-256': oids.SHA3_256,
|
|
83
|
+
'sha256': oids.SHA2_256,
|
|
84
|
+
'aes256-gcm': oids.AES_256_GCM,
|
|
85
|
+
'aes256-cbc': oids.AES_256_CBC
|
|
114
86
|
};
|
|
87
|
+
function assertCertificateAttributeNames(name) {
|
|
88
|
+
if (!(name in CertificateAttributeOIDDB)) {
|
|
89
|
+
throw (new Error(`Unknown attribute name: ${name}`));
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
function asCertificateAttributeNames(name) {
|
|
93
|
+
assertCertificateAttributeNames(name);
|
|
94
|
+
return (name);
|
|
95
|
+
}
|
|
96
|
+
function encodeAttribute(name, value) {
|
|
97
|
+
const schema = CertificateAttributeSchema[name];
|
|
98
|
+
const fieldNames = CertificateAttributeFieldNames[name];
|
|
99
|
+
// Date type
|
|
100
|
+
if (schema === ASN1.ValidateASN1.IsDate) {
|
|
101
|
+
if (!(util.types.isDate(value))) {
|
|
102
|
+
throw (new Error('Expected Date value'));
|
|
103
|
+
}
|
|
104
|
+
const asn1 = ASN1.JStoASN1(value);
|
|
105
|
+
const der = asn1.toBER(false);
|
|
106
|
+
return (der);
|
|
107
|
+
}
|
|
108
|
+
const MAX_ASN1_VALUE_DEPTH = 8; // Prevent excessive nesting
|
|
109
|
+
const toASN1Value = (input, depth = 0) => {
|
|
110
|
+
// Only allow primitives and raw binary that ASN1.JStoASN1 understands.
|
|
111
|
+
if (util.types.isDate(input)) {
|
|
112
|
+
return (input);
|
|
113
|
+
}
|
|
114
|
+
if (Buffer.isBuffer(input)) {
|
|
115
|
+
return (input);
|
|
116
|
+
}
|
|
117
|
+
if (input instanceof ArrayBuffer) {
|
|
118
|
+
return (arrayBufferToBuffer(input));
|
|
119
|
+
}
|
|
120
|
+
if (typeof input === 'string') {
|
|
121
|
+
return ({ type: 'string', kind: 'utf8', value: input });
|
|
122
|
+
}
|
|
123
|
+
/* XXX: Why are numbers and booleans encoded as strings? */
|
|
124
|
+
if (typeof input === 'number' || typeof input === 'bigint' || typeof input === 'boolean') {
|
|
125
|
+
return ({ type: 'string', kind: 'utf8', value: String(input) });
|
|
126
|
+
}
|
|
127
|
+
if (Array.isArray(input)) {
|
|
128
|
+
if (depth >= MAX_ASN1_VALUE_DEPTH) {
|
|
129
|
+
throw (new Error('Exceeded maximum ASN.1 value depth'));
|
|
130
|
+
}
|
|
131
|
+
return (input.map(item => toASN1Value(item, depth + 1)));
|
|
132
|
+
}
|
|
133
|
+
throw (new Error(`Unsupported ASN.1 value type: ${typeof input}`));
|
|
134
|
+
};
|
|
135
|
+
// Complex object type
|
|
136
|
+
if (fieldNames && hasIndexSignature(value) && !Array.isArray(value)) {
|
|
137
|
+
const mappedFields = fieldNames.map((fieldName, idx) => {
|
|
138
|
+
const fieldValue = value[fieldName];
|
|
139
|
+
if (fieldValue === undefined) {
|
|
140
|
+
return (undefined);
|
|
141
|
+
}
|
|
142
|
+
const tag = {
|
|
143
|
+
type: 'context',
|
|
144
|
+
kind: 'explicit',
|
|
145
|
+
value: idx,
|
|
146
|
+
contains: toASN1Value(fieldValue)
|
|
147
|
+
};
|
|
148
|
+
return (tag);
|
|
149
|
+
}).filter(function (computedValue) {
|
|
150
|
+
return (computedValue !== undefined);
|
|
151
|
+
});
|
|
152
|
+
const asn1 = ASN1.JStoASN1(mappedFields);
|
|
153
|
+
const der = asn1.toBER(false);
|
|
154
|
+
return (der);
|
|
155
|
+
}
|
|
156
|
+
throw (new Error(`Unsupported attribute value for encoding: ${JSON.stringify(DPO(value))}`));
|
|
157
|
+
}
|
|
158
|
+
// Prepare a value for inclusion in a SensitiveAttribute: pre-encode complex and date types
|
|
159
|
+
function encodeForSensitive(name, value) {
|
|
160
|
+
if (Buffer.isBuffer(value)) {
|
|
161
|
+
return (value);
|
|
162
|
+
}
|
|
163
|
+
if (value instanceof ArrayBuffer) {
|
|
164
|
+
return (arrayBufferToBuffer(value));
|
|
165
|
+
}
|
|
166
|
+
if (typeof value === 'string') {
|
|
167
|
+
const asn1 = ASN1.JStoASN1({ type: 'string', kind: 'utf8', value });
|
|
168
|
+
return (arrayBufferToBuffer(asn1.toBER(false)));
|
|
169
|
+
}
|
|
170
|
+
if (value instanceof Date) {
|
|
171
|
+
const asn1 = ASN1.JStoASN1(value);
|
|
172
|
+
return (arrayBufferToBuffer(asn1.toBER(false)));
|
|
173
|
+
}
|
|
174
|
+
if (typeof value === 'object' && value !== null) {
|
|
175
|
+
if (!name) {
|
|
176
|
+
throw (new Error('attributeName required for complex types'));
|
|
177
|
+
}
|
|
178
|
+
const encoded = encodeAttribute(name, value);
|
|
179
|
+
return (arrayBufferToBuffer(encoded));
|
|
180
|
+
}
|
|
181
|
+
return (Buffer.from(String(value), 'utf-8'));
|
|
182
|
+
}
|
|
183
|
+
async function decodeAttribute(name, value) {
|
|
184
|
+
const schema = CertificateAttributeSchema[name];
|
|
185
|
+
// XXX:TODO Fix depth issue
|
|
186
|
+
// @ts-ignore
|
|
187
|
+
const decodedUnknown = new ASN1.BufferStorageASN1(value, schema).getASN1();
|
|
188
|
+
const fieldNames = CertificateAttributeFieldNames[name];
|
|
189
|
+
let candidate;
|
|
190
|
+
if (fieldNames && Array.isArray(decodedUnknown)) {
|
|
191
|
+
const arr = decodedUnknown;
|
|
192
|
+
const result = {};
|
|
193
|
+
for (let i = 0; i < fieldNames.length; i++) {
|
|
194
|
+
const fieldName = fieldNames[i];
|
|
195
|
+
if (!fieldName) {
|
|
196
|
+
continue;
|
|
197
|
+
}
|
|
198
|
+
const fieldValue = arr[i];
|
|
199
|
+
if (fieldValue === undefined) {
|
|
200
|
+
continue;
|
|
201
|
+
}
|
|
202
|
+
if (isErrorLike(fieldValue)) {
|
|
203
|
+
throw (new Error(`Field ${fieldName} contains an error: ${fieldValue.message}`));
|
|
204
|
+
}
|
|
205
|
+
if (isContextTagged(fieldValue)) {
|
|
206
|
+
// unwrap context tag; prefer nested .value if present
|
|
207
|
+
result[fieldName] = hasValueProp(fieldValue.contains) ? fieldValue.contains.value : fieldValue.contains;
|
|
208
|
+
}
|
|
209
|
+
else if (hasValueProp(fieldValue)) {
|
|
210
|
+
result[fieldName] = fieldValue.value;
|
|
211
|
+
}
|
|
212
|
+
else {
|
|
213
|
+
result[fieldName] = fieldValue;
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
candidate = result;
|
|
217
|
+
}
|
|
218
|
+
else if (hasValueProp(decodedUnknown)) {
|
|
219
|
+
candidate = decodedUnknown.value;
|
|
220
|
+
}
|
|
221
|
+
else {
|
|
222
|
+
candidate = decodedUnknown;
|
|
223
|
+
}
|
|
224
|
+
return (asAttributeValue(name, candidate));
|
|
225
|
+
}
|
|
115
226
|
class SensitiveAttributeBuilder {
|
|
116
227
|
#account;
|
|
117
228
|
#value;
|
|
118
|
-
constructor(account
|
|
229
|
+
constructor(account) {
|
|
119
230
|
this.#account = account;
|
|
120
|
-
if (value) {
|
|
121
|
-
this.set(value);
|
|
122
|
-
}
|
|
123
231
|
}
|
|
124
232
|
set(value) {
|
|
125
|
-
|
|
126
|
-
this.#value = value;
|
|
127
|
-
}
|
|
128
|
-
else if (typeof value === 'string') {
|
|
129
|
-
this.#value = Buffer.from(value, 'utf-8');
|
|
130
|
-
}
|
|
131
|
-
else {
|
|
132
|
-
this.#value = Buffer.from(value);
|
|
133
|
-
}
|
|
233
|
+
this.#value = Buffer.isBuffer(value) ? value : arrayBufferLikeToBuffer(value);
|
|
134
234
|
return (this);
|
|
135
235
|
}
|
|
136
236
|
async build() {
|
|
@@ -143,15 +243,31 @@ class SensitiveAttributeBuilder {
|
|
|
143
243
|
const cipher = 'aes-256-gcm';
|
|
144
244
|
const key = crypto.randomBytes(32);
|
|
145
245
|
const nonce = crypto.randomBytes(12);
|
|
146
|
-
const encryptedKey = await this.#account.encrypt(key);
|
|
246
|
+
const encryptedKey = await this.#account.encrypt(bufferToArrayBuffer(key));
|
|
147
247
|
function encrypt(value) {
|
|
148
248
|
const cipherObject = crypto.createCipheriv(cipher, key, nonce);
|
|
149
249
|
let retval = cipherObject.update(value);
|
|
150
250
|
retval = Buffer.concat([retval, cipherObject.final()]);
|
|
251
|
+
/*
|
|
252
|
+
* For AES-GCM, the last 16 bytes are the authentication tag
|
|
253
|
+
*/
|
|
254
|
+
if (cipher === 'aes-256-gcm') {
|
|
255
|
+
const getAuthTagFn = Reflect.get(cipherObject, 'getAuthTag');
|
|
256
|
+
if (typeof getAuthTagFn === 'function') {
|
|
257
|
+
const tag = getAuthTagFn.call(cipherObject);
|
|
258
|
+
if (!Buffer.isBuffer(tag)) {
|
|
259
|
+
throw (new Error('getAuthTag did not return a Buffer'));
|
|
260
|
+
}
|
|
261
|
+
retval = Buffer.concat([retval, tag]);
|
|
262
|
+
}
|
|
263
|
+
else {
|
|
264
|
+
throw (new Error('getAuthTag is not available on cipherObject'));
|
|
265
|
+
}
|
|
266
|
+
}
|
|
151
267
|
return (retval);
|
|
152
268
|
}
|
|
153
269
|
const encryptedValue = encrypt(this.#value);
|
|
154
|
-
const encryptedSalt = encrypt(salt);
|
|
270
|
+
const encryptedSalt = encrypt(arrayBufferLikeToBuffer(salt));
|
|
155
271
|
const saltedValue = Buffer.concat([salt, publicKey, encryptedValue, this.#value]);
|
|
156
272
|
const hashedAndSaltedValue = KeetaNetClient.lib.Utils.Hash.Hash(saltedValue);
|
|
157
273
|
const attributeStructure = [
|
|
@@ -179,23 +295,35 @@ class SensitiveAttributeBuilder {
|
|
|
179
295
|
encryptedValue
|
|
180
296
|
];
|
|
181
297
|
const encodedAttributeObject = ASN1.JStoASN1(attributeStructure);
|
|
182
|
-
|
|
298
|
+
// Produce canonical DER as ArrayBuffer
|
|
299
|
+
const retval = encodedAttributeObject.toBER(false);
|
|
183
300
|
return (retval);
|
|
184
301
|
}
|
|
185
302
|
}
|
|
186
303
|
class SensitiveAttribute {
|
|
187
304
|
#account;
|
|
188
305
|
#info;
|
|
189
|
-
|
|
306
|
+
#decoder;
|
|
307
|
+
constructor(account, data, decoder) {
|
|
190
308
|
this.#account = account;
|
|
191
309
|
this.#info = this.decode(data);
|
|
310
|
+
if (decoder) {
|
|
311
|
+
this.#decoder = decoder;
|
|
312
|
+
}
|
|
192
313
|
}
|
|
193
314
|
decode(data) {
|
|
194
315
|
if (Buffer.isBuffer(data)) {
|
|
195
316
|
data = bufferToArrayBuffer(data);
|
|
196
317
|
}
|
|
197
|
-
|
|
198
|
-
|
|
318
|
+
let decodedAttribute;
|
|
319
|
+
try {
|
|
320
|
+
const dataObject = new ASN1.BufferStorageASN1(data, SensitiveAttributeSchemaInternal);
|
|
321
|
+
decodedAttribute = dataObject.getASN1();
|
|
322
|
+
}
|
|
323
|
+
catch {
|
|
324
|
+
const js = ASN1toJS(data);
|
|
325
|
+
throw (new Error(`SensitiveAttribute.decode: unexpected DER shape ${JSON.stringify(DPO(js))}`));
|
|
326
|
+
}
|
|
199
327
|
const decodedVersion = decodedAttribute[0] + 1n;
|
|
200
328
|
if (decodedVersion !== 1n) {
|
|
201
329
|
throw (new Error(`Unsupported Sensitive Attribute version (${decodedVersion})`));
|
|
@@ -217,11 +345,31 @@ class SensitiveAttribute {
|
|
|
217
345
|
});
|
|
218
346
|
}
|
|
219
347
|
async #decryptValue(value) {
|
|
220
|
-
const decryptedKey = await this.#account.decrypt(this.#info.cipher.key);
|
|
348
|
+
const decryptedKey = await this.#account.decrypt(bufferToArrayBuffer(this.#info.cipher.key));
|
|
221
349
|
const algorithm = this.#info.cipher.algorithm;
|
|
222
350
|
const iv = this.#info.cipher.iv;
|
|
223
351
|
const cipher = crypto.createDecipheriv(algorithm, Buffer.from(decryptedKey), iv);
|
|
352
|
+
// For AES-GCM, the last 16 bytes are the authentication tag
|
|
353
|
+
if (algorithm === 'aes-256-gcm') {
|
|
354
|
+
const authTag = value.subarray(value.length - 16);
|
|
355
|
+
const ciphertext = value.subarray(0, value.length - 16);
|
|
356
|
+
// XXX:TODO Fix typescript unsafe calls
|
|
357
|
+
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
|
|
358
|
+
const setAuthTagFn = Reflect.get(cipher, 'setAuthTag');
|
|
359
|
+
if (typeof setAuthTagFn === 'function') {
|
|
360
|
+
// eslint-disable-next-line @typescript-eslint/no-unsafe-call, @typescript-eslint/no-unsafe-member-access
|
|
361
|
+
setAuthTagFn.call(cipher, authTag);
|
|
362
|
+
}
|
|
363
|
+
else {
|
|
364
|
+
throw (new Error('setAuthTag is not available on cipher'));
|
|
365
|
+
}
|
|
366
|
+
const decrypted = cipher.update(ciphertext);
|
|
367
|
+
cipher.final(); // Verify auth tag
|
|
368
|
+
return (decrypted);
|
|
369
|
+
}
|
|
370
|
+
// For other algorithms (like CBC), just decrypt normally
|
|
224
371
|
const decryptedValue = cipher.update(value);
|
|
372
|
+
cipher.final();
|
|
225
373
|
return (decryptedValue);
|
|
226
374
|
}
|
|
227
375
|
/**
|
|
@@ -234,25 +382,33 @@ class SensitiveAttribute {
|
|
|
234
382
|
* ArrayBuffer
|
|
235
383
|
*/
|
|
236
384
|
async get() {
|
|
237
|
-
const decryptedValue = await this.#decryptValue(this.#info.encryptedValue);
|
|
385
|
+
const decryptedValue = await this.#decryptValue(arrayBufferLikeToBuffer(this.#info.encryptedValue));
|
|
238
386
|
return (bufferToArrayBuffer(decryptedValue));
|
|
239
387
|
}
|
|
240
|
-
|
|
241
|
-
* Get the value of the sensitive attribute as a string after being
|
|
242
|
-
* interpreted as UTF-8 ( @see SensitiveAttribute.get for more information)
|
|
243
|
-
*/
|
|
244
|
-
async getString() {
|
|
388
|
+
async getValue() {
|
|
245
389
|
const value = await this.get();
|
|
246
|
-
|
|
390
|
+
if (!this.#decoder) {
|
|
391
|
+
/**
|
|
392
|
+
* TypeScript complains that T may not be the correct
|
|
393
|
+
* type here, but gives us no tools to enforce that it
|
|
394
|
+
* is -- it should always be ArrayBuffer if no decoder
|
|
395
|
+
* is provided, but someone could always specify a
|
|
396
|
+
* type parameter in that case and we cannot check
|
|
397
|
+
* that at runtime since T is only a compile-time type.
|
|
398
|
+
*/
|
|
399
|
+
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
400
|
+
return value;
|
|
401
|
+
}
|
|
402
|
+
return (this.#decoder(value));
|
|
247
403
|
}
|
|
248
404
|
/**
|
|
249
405
|
* Generate a proof that a sensitive attribute is a given value,
|
|
250
406
|
* which can be validated by a third party using the certificate
|
|
251
407
|
* and the `validateProof` method
|
|
252
408
|
*/
|
|
253
|
-
async
|
|
409
|
+
async getProof() {
|
|
254
410
|
const value = await this.get();
|
|
255
|
-
const salt = await this.#decryptValue(this.#info.hashedValue.encryptedSalt);
|
|
411
|
+
const salt = await this.#decryptValue(arrayBufferLikeToBuffer(this.#info.hashedValue.encryptedSalt));
|
|
256
412
|
return ({
|
|
257
413
|
value: Buffer.from(value).toString('base64'),
|
|
258
414
|
hash: {
|
|
@@ -268,7 +424,8 @@ class SensitiveAttribute {
|
|
|
268
424
|
const proofSaltBuffer = Buffer.from(proof.hash.salt, 'base64');
|
|
269
425
|
const publicKeyBuffer = Buffer.from(this.#account.publicKey.get());
|
|
270
426
|
const encryptedValue = this.#info.encryptedValue;
|
|
271
|
-
const
|
|
427
|
+
const hashInput = Buffer.concat([proofSaltBuffer, publicKeyBuffer, encryptedValue, plaintextValue]);
|
|
428
|
+
const hashedAndSaltedValue = KeetaNetClient.lib.Utils.Hash.Hash(hashInput);
|
|
272
429
|
const hashedAndSaltedValueBuffer = Buffer.from(hashedAndSaltedValue);
|
|
273
430
|
return (this.#info.hashedValue.value.equals(hashedAndSaltedValueBuffer));
|
|
274
431
|
}
|
|
@@ -276,16 +433,6 @@ class SensitiveAttribute {
|
|
|
276
433
|
return (toJSON(this.#info));
|
|
277
434
|
}
|
|
278
435
|
}
|
|
279
|
-
/**
|
|
280
|
-
* Database of attributes
|
|
281
|
-
*/
|
|
282
|
-
const CertificateAttributeOIDDB = {
|
|
283
|
-
'fullName': '1.3.6.1.4.1.62675.1.0',
|
|
284
|
-
'dateOfBirth': '1.3.6.1.4.1.62675.1.1',
|
|
285
|
-
'address': '1.3.6.1.4.1.62675.1.2',
|
|
286
|
-
'email': '1.3.6.1.4.1.62675.1.3',
|
|
287
|
-
'phoneNumber': '1.3.6.1.4.1.62675.1.4'
|
|
288
|
-
};
|
|
289
436
|
/**
|
|
290
437
|
* ASN.1 Schema for Certificate KYC Attributes Extension
|
|
291
438
|
*
|
|
@@ -338,10 +485,40 @@ export class CertificateBuilder extends KeetaNetClient.lib.Utils.Certificate.Cer
|
|
|
338
485
|
super(CertificateBuilder.mapParams(params));
|
|
339
486
|
}
|
|
340
487
|
/**
|
|
341
|
-
* Set a KYC Attribute to a given value
|
|
488
|
+
* Set a KYC Attribute to a given value.
|
|
489
|
+
* The sensitive flag is required.
|
|
490
|
+
*
|
|
491
|
+
* If an attribute is marked sensitive, the value is encoded
|
|
492
|
+
* into the certificate using a commitment scheme so that the
|
|
493
|
+
* value can be proven later without revealing it.
|
|
342
494
|
*/
|
|
343
495
|
setAttribute(name, sensitive, value) {
|
|
344
|
-
|
|
496
|
+
// Non-sensitive path: only primitive schema (string/date) allowed
|
|
497
|
+
const schemaValidator = CertificateAttributeSchema[name];
|
|
498
|
+
let encoded;
|
|
499
|
+
if (value instanceof ArrayBuffer) {
|
|
500
|
+
encoded = value;
|
|
501
|
+
}
|
|
502
|
+
else if (name in CertificateAttributeSchema) {
|
|
503
|
+
/* XXX: Why do we have two encoding methods ? */
|
|
504
|
+
encoded = bufferToArrayBuffer(encodeForSensitive(name, value));
|
|
505
|
+
}
|
|
506
|
+
else if (schemaValidator === ASN1.ValidateASN1.IsDate) {
|
|
507
|
+
if (!(value instanceof Date)) {
|
|
508
|
+
throw (new Error('Expected Date value'));
|
|
509
|
+
}
|
|
510
|
+
encoded = encodeAttribute(name, value);
|
|
511
|
+
}
|
|
512
|
+
else if (schemaValidator === ASN1.ValidateASN1.IsString && typeof value === 'string') {
|
|
513
|
+
encoded = encodeAttribute(name, value);
|
|
514
|
+
}
|
|
515
|
+
else {
|
|
516
|
+
throw (new Error('Unsupported non-sensitive value type'));
|
|
517
|
+
}
|
|
518
|
+
this.#attributes[name] = {
|
|
519
|
+
sensitive: sensitive,
|
|
520
|
+
value: encoded
|
|
521
|
+
};
|
|
345
522
|
}
|
|
346
523
|
async addExtensions(...args) {
|
|
347
524
|
const retval = await super.addExtensions(...args);
|
|
@@ -357,19 +534,20 @@ export class CertificateBuilder extends KeetaNetClient.lib.Utils.Certificate.Cer
|
|
|
357
534
|
* can assume that the attribute is always present in
|
|
358
535
|
* the object
|
|
359
536
|
*/
|
|
360
|
-
|
|
537
|
+
assertCertificateAttributeNames(name);
|
|
361
538
|
const nameOID = CertificateAttributeOIDDB[name];
|
|
362
539
|
let value;
|
|
363
540
|
if (attribute.sensitive) {
|
|
364
|
-
const
|
|
365
|
-
|
|
541
|
+
const builder = new SensitiveAttributeBuilder(subject);
|
|
542
|
+
builder.set(attribute.value);
|
|
543
|
+
value = arrayBufferToBuffer(await builder.build());
|
|
366
544
|
}
|
|
367
545
|
else {
|
|
368
546
|
if (typeof attribute.value === 'string') {
|
|
369
547
|
value = Buffer.from(attribute.value, 'utf-8');
|
|
370
548
|
}
|
|
371
549
|
else {
|
|
372
|
-
value =
|
|
550
|
+
value = arrayBufferToBuffer(attribute.value);
|
|
373
551
|
}
|
|
374
552
|
}
|
|
375
553
|
certAttributes.push([{
|
|
@@ -383,7 +561,7 @@ export class CertificateBuilder extends KeetaNetClient.lib.Utils.Certificate.Cer
|
|
|
383
561
|
}]);
|
|
384
562
|
}
|
|
385
563
|
if (certAttributes.length > 0) {
|
|
386
|
-
retval.push(KeetaNetClient.lib.Utils.Certificate.CertificateBuilder.extension(
|
|
564
|
+
retval.push(KeetaNetClient.lib.Utils.Certificate.CertificateBuilder.extension(oids.keeta.KYC_ATTRIBUTES, certAttributes));
|
|
387
565
|
}
|
|
388
566
|
return (retval);
|
|
389
567
|
}
|
|
@@ -412,6 +590,7 @@ export class CertificateBuilder extends KeetaNetClient.lib.Utils.Certificate.Cer
|
|
|
412
590
|
export class Certificate extends KeetaNetClient.lib.Utils.Certificate.Certificate {
|
|
413
591
|
subjectKey;
|
|
414
592
|
static Builder = CertificateBuilder;
|
|
593
|
+
static SharableAttributes;
|
|
415
594
|
/**
|
|
416
595
|
* User KYC Attributes
|
|
417
596
|
*/
|
|
@@ -424,27 +603,56 @@ export class Certificate extends KeetaNetClient.lib.Utils.Certificate.Certificat
|
|
|
424
603
|
finalizeConstruction() {
|
|
425
604
|
/* Do nothing, we call the super method in the constructor */
|
|
426
605
|
}
|
|
606
|
+
setPlainAttribute(name, value) {
|
|
607
|
+
// @ts-ignore
|
|
608
|
+
this.attributes[name] = { sensitive: false, value };
|
|
609
|
+
}
|
|
610
|
+
setSensitiveAttribute(name, value) {
|
|
611
|
+
this.attributes[name] = {
|
|
612
|
+
sensitive: true,
|
|
613
|
+
value: new SensitiveAttribute(this.subjectKey, value, decodeAttribute.bind(null, name))
|
|
614
|
+
};
|
|
615
|
+
}
|
|
616
|
+
/**
|
|
617
|
+
* Get the underlying value for an attribute.
|
|
618
|
+
*
|
|
619
|
+
* If the attribute is sensitive, this will decrypt it using the
|
|
620
|
+
* subject's private key, otherwise it will return the value.
|
|
621
|
+
*/
|
|
622
|
+
async getAttributeValue(attributeName) {
|
|
623
|
+
const attr = this.attributes[attributeName]?.value;
|
|
624
|
+
if (!attr) {
|
|
625
|
+
throw (new Error(`Attribute ${attributeName} is not available`));
|
|
626
|
+
}
|
|
627
|
+
if (attr instanceof SensitiveAttribute) {
|
|
628
|
+
const raw = await attr.get();
|
|
629
|
+
return (await decodeAttribute(attributeName, raw));
|
|
630
|
+
}
|
|
631
|
+
// Non-sensitive: ArrayBuffer or Buffer
|
|
632
|
+
if (attr instanceof ArrayBuffer || Buffer.isBuffer(attr)) {
|
|
633
|
+
return (await decodeAttribute(attributeName, attr));
|
|
634
|
+
}
|
|
635
|
+
throw (new Error(`Attribute ${attributeName} is not a supported type`));
|
|
636
|
+
}
|
|
427
637
|
processExtension(id, value) {
|
|
428
638
|
if (super.processExtension(id, value)) {
|
|
429
639
|
return (true);
|
|
430
640
|
}
|
|
431
|
-
if (id ===
|
|
641
|
+
if (id === oids.keeta.KYC_ATTRIBUTES) {
|
|
432
642
|
const attributesRaw = new ASN1.BufferStorageASN1(value, CertificateKYCAttributeSchemaValidation).getASN1();
|
|
433
643
|
for (const attribute of attributesRaw) {
|
|
434
|
-
const
|
|
644
|
+
const nameString = lookupByOID(attribute[0].oid, CertificateAttributeOIDDB);
|
|
645
|
+
const name = asCertificateAttributeNames(nameString);
|
|
435
646
|
const valueKind = attribute[1].value;
|
|
436
647
|
const value = bufferToArrayBuffer(attribute[1].contains);
|
|
437
648
|
switch (valueKind) {
|
|
438
649
|
case 0:
|
|
439
650
|
/* Plain Value */
|
|
440
|
-
this.
|
|
651
|
+
this.setPlainAttribute(name, value);
|
|
441
652
|
break;
|
|
442
653
|
case 1:
|
|
443
654
|
/* Sensitive Value */
|
|
444
|
-
this.
|
|
445
|
-
sensitive: true,
|
|
446
|
-
value: new SensitiveAttribute(this.subjectKey, value)
|
|
447
|
-
};
|
|
655
|
+
this.setSensitiveAttribute(name, value);
|
|
448
656
|
break;
|
|
449
657
|
default:
|
|
450
658
|
assertNever(valueKind);
|
|
@@ -455,6 +663,260 @@ export class Certificate extends KeetaNetClient.lib.Utils.Certificate.Certificat
|
|
|
455
663
|
return (false);
|
|
456
664
|
}
|
|
457
665
|
}
|
|
666
|
+
;
|
|
667
|
+
export class SharableCertificateAttributes {
|
|
668
|
+
#certificate;
|
|
669
|
+
#attributes = {};
|
|
670
|
+
container;
|
|
671
|
+
populatedFromInit = false;
|
|
672
|
+
static assertCertificateAttributeName = assertCertificateAttributeNames;
|
|
673
|
+
constructor(input, options) {
|
|
674
|
+
let containerBuffer;
|
|
675
|
+
if (typeof input === 'string') {
|
|
676
|
+
/*
|
|
677
|
+
* Attempt to decode as PEM, but also if not PEM, then return
|
|
678
|
+
* the lines as-is (base64) after removing whitespace
|
|
679
|
+
*/
|
|
680
|
+
const inputLines = input.split(/\r?\n/);
|
|
681
|
+
let base64Lines;
|
|
682
|
+
for (let beginOffset = 0; beginOffset < inputLines.length; beginOffset++) {
|
|
683
|
+
const line = inputLines[beginOffset]?.trim();
|
|
684
|
+
if (line?.startsWith('-----BEGIN ')) {
|
|
685
|
+
let endIndex = -1;
|
|
686
|
+
const matchingEndLine = line.replace('BEGIN', 'END');
|
|
687
|
+
for (let endOffset = beginOffset + 1; endOffset < inputLines.length; endOffset++) {
|
|
688
|
+
const checkEndLine = inputLines[endOffset]?.trim();
|
|
689
|
+
if (checkEndLine === matchingEndLine) {
|
|
690
|
+
endIndex = endOffset;
|
|
691
|
+
break;
|
|
692
|
+
}
|
|
693
|
+
}
|
|
694
|
+
if (endIndex === -1) {
|
|
695
|
+
throw (new Error('Invalid PEM format: missing END line'));
|
|
696
|
+
}
|
|
697
|
+
base64Lines = inputLines.slice(beginOffset + 1, endIndex);
|
|
698
|
+
break;
|
|
699
|
+
}
|
|
700
|
+
}
|
|
701
|
+
if (base64Lines === undefined) {
|
|
702
|
+
base64Lines = inputLines;
|
|
703
|
+
}
|
|
704
|
+
base64Lines = base64Lines.map(function (line) {
|
|
705
|
+
return (line.trim());
|
|
706
|
+
}).filter(function (line) {
|
|
707
|
+
return (line.length > 0);
|
|
708
|
+
});
|
|
709
|
+
const base64Content = base64Lines.join('');
|
|
710
|
+
containerBuffer = Buffer.from(base64Content, 'base64');
|
|
711
|
+
}
|
|
712
|
+
else {
|
|
713
|
+
containerBuffer = arrayBufferToBuffer(input);
|
|
714
|
+
}
|
|
715
|
+
let principals = options?.principals;
|
|
716
|
+
if (KeetaNetAccount.isInstance(principals)) {
|
|
717
|
+
principals = [principals];
|
|
718
|
+
}
|
|
719
|
+
else if (principals instanceof Set) {
|
|
720
|
+
principals = Array.from(principals);
|
|
721
|
+
}
|
|
722
|
+
else if (principals === undefined) {
|
|
723
|
+
principals = null;
|
|
724
|
+
}
|
|
725
|
+
this.container = EncryptedContainer.fromEncodedBuffer(containerBuffer, principals);
|
|
726
|
+
}
|
|
727
|
+
/**
|
|
728
|
+
* Create a SharableCertificateAttributes from a Certificate
|
|
729
|
+
* and a list of attribute names to include -- if no list is
|
|
730
|
+
* provided, all attributes are included.
|
|
731
|
+
*/
|
|
732
|
+
static async fromCertificate(certificate, attributeNames) {
|
|
733
|
+
if (attributeNames === undefined) {
|
|
734
|
+
/*
|
|
735
|
+
* We know the keys are whatever the Certificate says they are, so
|
|
736
|
+
* we can cast here safely
|
|
737
|
+
*/
|
|
738
|
+
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
739
|
+
attributeNames = Object.keys(certificate.attributes);
|
|
740
|
+
}
|
|
741
|
+
const attributes = {};
|
|
742
|
+
for (const name of attributeNames) {
|
|
743
|
+
const attr = certificate.attributes[name];
|
|
744
|
+
/**
|
|
745
|
+
* Skip missing attributes
|
|
746
|
+
*/
|
|
747
|
+
if (!attr) {
|
|
748
|
+
continue;
|
|
749
|
+
}
|
|
750
|
+
if (attr.sensitive) {
|
|
751
|
+
attributes[name] = {
|
|
752
|
+
sensitive: true,
|
|
753
|
+
value: await attr.value.getProof()
|
|
754
|
+
};
|
|
755
|
+
}
|
|
756
|
+
else {
|
|
757
|
+
attributes[name] = {
|
|
758
|
+
sensitive: false,
|
|
759
|
+
value: arrayBufferToBuffer(attr.value).toString('base64')
|
|
760
|
+
};
|
|
761
|
+
}
|
|
762
|
+
}
|
|
763
|
+
const contentsString = JSON.stringify({
|
|
764
|
+
certificate: certificate.toPEM(),
|
|
765
|
+
attributes: attributes
|
|
766
|
+
});
|
|
767
|
+
const temporaryUser = KeetaNetAccount.fromSeed(KeetaNetAccount.generateRandomSeed(), 0);
|
|
768
|
+
const contentsBuffer = Buffer.from(contentsString, 'utf-8');
|
|
769
|
+
const contentsBufferCompressed = await KeetaNetClient.lib.Utils.Buffer.ZlibDeflateAsync(bufferToArrayBuffer(contentsBuffer));
|
|
770
|
+
const container = EncryptedContainer.fromPlaintext(arrayBufferToBuffer(contentsBufferCompressed), [temporaryUser], true);
|
|
771
|
+
const containerBuffer = await container.getEncodedBuffer();
|
|
772
|
+
const retval = new SharableCertificateAttributes(bufferToArrayBuffer(containerBuffer), { principals: temporaryUser });
|
|
773
|
+
await retval.revokeAccess(temporaryUser);
|
|
774
|
+
return (retval);
|
|
775
|
+
}
|
|
776
|
+
async grantAccess(principal) {
|
|
777
|
+
await this.container.grantAccess(principal);
|
|
778
|
+
return (this);
|
|
779
|
+
}
|
|
780
|
+
async revokeAccess(principal) {
|
|
781
|
+
await this.container.revokeAccess(principal);
|
|
782
|
+
return (this);
|
|
783
|
+
}
|
|
784
|
+
get principals() {
|
|
785
|
+
return (this.container.principals);
|
|
786
|
+
}
|
|
787
|
+
async #populate() {
|
|
788
|
+
if (this.populatedFromInit) {
|
|
789
|
+
return;
|
|
790
|
+
}
|
|
791
|
+
this.populatedFromInit = true;
|
|
792
|
+
const contentsBuffer = await this.container.getPlaintext();
|
|
793
|
+
const contentsBufferDecompressed = await KeetaNetClient.lib.Utils.Buffer.ZlibInflateAsync(bufferToArrayBuffer(contentsBuffer));
|
|
794
|
+
const contentsString = Buffer.from(contentsBufferDecompressed).toString('utf-8');
|
|
795
|
+
const contentsJSON = JSON.parse(contentsString);
|
|
796
|
+
const contents = assertSharableCertificateAttributesContentsSchema(contentsJSON);
|
|
797
|
+
this.#certificate = new Certificate(contents.certificate);
|
|
798
|
+
const attributePromises = Object.entries(contents.attributes).map(async ([name, attr]) => {
|
|
799
|
+
/*
|
|
800
|
+
* Get the corresponding attribute from the certificate
|
|
801
|
+
*
|
|
802
|
+
* We actually do not care if `name` is a known attribute
|
|
803
|
+
* because we are not decoding it here, we are just
|
|
804
|
+
* verifying it matches the certificate
|
|
805
|
+
*/
|
|
806
|
+
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
807
|
+
const certAttribute = this.#certificate?.attributes[name];
|
|
808
|
+
if (!certAttribute) {
|
|
809
|
+
throw (new Error(`Attribute ${name} not found in certificate`));
|
|
810
|
+
}
|
|
811
|
+
if (certAttribute.sensitive !== attr.sensitive) {
|
|
812
|
+
throw (new Error(`Attribute ${name} sensitivity mismatch with certificate`));
|
|
813
|
+
}
|
|
814
|
+
if (!attr.sensitive) {
|
|
815
|
+
if (certAttribute.sensitive) {
|
|
816
|
+
throw (new Error(`Attribute ${name} sensitivity mismatch with certificate`));
|
|
817
|
+
}
|
|
818
|
+
const certValue = certAttribute.value;
|
|
819
|
+
const sharedValue = bufferToArrayBuffer(Buffer.from(attr.value, 'base64'));
|
|
820
|
+
if (sharedValue.byteLength !== certValue.byteLength || !Buffer.from(sharedValue).equals(Buffer.from(certValue))) {
|
|
821
|
+
throw (new Error(`Attribute ${name} value mismatch with certificate`));
|
|
822
|
+
}
|
|
823
|
+
return ([name, {
|
|
824
|
+
sensitive: false,
|
|
825
|
+
value: sharedValue
|
|
826
|
+
}]);
|
|
827
|
+
}
|
|
828
|
+
if (!certAttribute.sensitive) {
|
|
829
|
+
throw (new Error(`Attribute ${name} sensitivity mismatch with certificate`));
|
|
830
|
+
}
|
|
831
|
+
if (!(await certAttribute.value.validateProof(attr.value))) {
|
|
832
|
+
throw (new Error(`Attribute ${name} proof validation failed`));
|
|
833
|
+
}
|
|
834
|
+
const attrValue = bufferToArrayBuffer(Buffer.from(attr.value.value, 'base64'));
|
|
835
|
+
return ([name, {
|
|
836
|
+
sensitive: true,
|
|
837
|
+
value: attrValue
|
|
838
|
+
}]);
|
|
839
|
+
});
|
|
840
|
+
const resolvedAttributes = await Promise.all(attributePromises);
|
|
841
|
+
this.#attributes = Object.fromEntries(resolvedAttributes);
|
|
842
|
+
}
|
|
843
|
+
async getCertificate() {
|
|
844
|
+
await this.#populate();
|
|
845
|
+
if (!this.#certificate) {
|
|
846
|
+
throw (new Error('internal error: certificate not populated'));
|
|
847
|
+
}
|
|
848
|
+
return (this.#certificate);
|
|
849
|
+
}
|
|
850
|
+
async getAttributeBuffer(name) {
|
|
851
|
+
await this.#populate();
|
|
852
|
+
const attr = this.#attributes[name];
|
|
853
|
+
return (attr?.value);
|
|
854
|
+
}
|
|
855
|
+
async getAttribute(name) {
|
|
856
|
+
const buffer = await this.getAttributeBuffer(name);
|
|
857
|
+
if (buffer === undefined) {
|
|
858
|
+
return (undefined);
|
|
859
|
+
}
|
|
860
|
+
const retval = await decodeAttribute(name, buffer);
|
|
861
|
+
/* XXX:TODO: Here is where we would look at a reference value
|
|
862
|
+
* (e.g., URL+hash) and fetch it, and verify it the hash matches
|
|
863
|
+
* the fetched value
|
|
864
|
+
*
|
|
865
|
+
* The schema for references is not yet defined, so this is
|
|
866
|
+
* left as a TODO for now.
|
|
867
|
+
*
|
|
868
|
+
* The return type would also need to be updated to reflect
|
|
869
|
+
* that we would map referenced types to something like
|
|
870
|
+
* { data: ArrayBuffer, contentType: string, source: <url>,
|
|
871
|
+
* hash: <hash> } (where source and hash should be named
|
|
872
|
+
* after whatever the actual schema is)
|
|
873
|
+
*/
|
|
874
|
+
return (retval);
|
|
875
|
+
}
|
|
876
|
+
async getAttributeNames(includeUnknown) {
|
|
877
|
+
await this.#populate();
|
|
878
|
+
const names = Object.keys(this.#attributes);
|
|
879
|
+
if (includeUnknown) {
|
|
880
|
+
return (names);
|
|
881
|
+
}
|
|
882
|
+
const knownNames = names.filter(function (name) {
|
|
883
|
+
return (name in CertificateAttributeOIDDB);
|
|
884
|
+
});
|
|
885
|
+
return (knownNames);
|
|
886
|
+
}
|
|
887
|
+
async export(options) {
|
|
888
|
+
options = {
|
|
889
|
+
format: 'arraybuffer',
|
|
890
|
+
...options
|
|
891
|
+
};
|
|
892
|
+
let principals;
|
|
893
|
+
try {
|
|
894
|
+
principals = this.container.principals;
|
|
895
|
+
}
|
|
896
|
+
catch {
|
|
897
|
+
principals = [];
|
|
898
|
+
}
|
|
899
|
+
if (principals.length === 0) {
|
|
900
|
+
throw (new Error('This container has no authorized users (principals); cannot export'));
|
|
901
|
+
}
|
|
902
|
+
const retvalBuffer = await this.container.getEncodedBuffer();
|
|
903
|
+
if (options.format === 'string') {
|
|
904
|
+
const retvalBase64 = retvalBuffer.toString('base64');
|
|
905
|
+
const retvalLines = ['-----BEGIN KYC CERTIFICATE PROOF-----'];
|
|
906
|
+
retvalLines.push(...retvalBase64.match(/.{1,64}/g) ?? []);
|
|
907
|
+
retvalLines.push('-----END KYC CERTIFICATE PROOF-----');
|
|
908
|
+
return (retvalLines.join('\n'));
|
|
909
|
+
}
|
|
910
|
+
else if (options.format === 'arraybuffer') {
|
|
911
|
+
return (bufferToArrayBuffer(retvalBuffer));
|
|
912
|
+
}
|
|
913
|
+
else {
|
|
914
|
+
throw (new Error(`Unsupported export format: ${String(options.format)}`));
|
|
915
|
+
}
|
|
916
|
+
}
|
|
917
|
+
}
|
|
918
|
+
// @ts-ignore
|
|
919
|
+
Certificate.SharableAttributes = SharableCertificateAttributes;
|
|
458
920
|
/** @internal */
|
|
459
921
|
export const _Testing = {
|
|
460
922
|
SensitiveAttributeBuilder,
|