@icure/api 4.2.31 → 5.0.3
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/icc-api/api/IccDeviceApi.d.ts +3 -3
- package/icc-api/api/IccDeviceApi.js.map +1 -1
- package/icc-api/api/IccHcpartyApi.d.ts +3 -3
- package/icc-api/api/IccHcpartyApi.js.map +1 -1
- package/icc-api/api/IccPatientApi.d.ts +3 -3
- package/icc-api/api/IccPatientApi.js.map +1 -1
- package/icc-api/api/IccUserApi.js +1 -1
- package/icc-api/api/IccUserApi.js.map +1 -1
- package/icc-api/api/XHR.js +2 -1
- package/icc-api/api/XHR.js.map +1 -1
- package/icc-api/iccApi.js +5 -1
- package/icc-api/iccApi.js.map +1 -1
- package/icc-api/index.js +5 -1
- package/icc-api/index.js.map +1 -1
- package/icc-api/model/Body.js +1 -1
- package/icc-api/model/Body.js.map +1 -1
- package/icc-api/model/Body1.js +1 -1
- package/icc-api/model/Body1.js.map +1 -1
- package/icc-api/model/Body2.js +1 -1
- package/icc-api/model/Body2.js.map +1 -1
- package/icc-api/model/Body3.js +1 -1
- package/icc-api/model/Body3.js.map +1 -1
- package/icc-api/model/ByteArray.js +1 -1
- package/icc-api/model/ByteArray.js.map +1 -1
- package/icc-api/model/Content.js +1 -1
- package/icc-api/model/Content.js.map +1 -1
- package/icc-api/model/Device.js +1 -1
- package/icc-api/model/Device.js.map +1 -1
- package/icc-api/model/Document.js +1 -1
- package/icc-api/model/Document.js.map +1 -1
- package/icc-api/model/DocumentTemplate.js +1 -1
- package/icc-api/model/DocumentTemplate.js.map +1 -1
- package/icc-api/model/HealthcareParty.js +1 -1
- package/icc-api/model/HealthcareParty.js.map +1 -1
- package/icc-api/model/MimeAttachment.js +1 -1
- package/icc-api/model/MimeAttachment.js.map +1 -1
- package/icc-api/model/Patient.js +1 -1
- package/icc-api/model/Patient.js.map +1 -1
- package/icc-api/model/models.d.ts +15 -0
- package/icc-api/model/models.js +5 -1
- package/icc-api/model/models.js.map +1 -1
- package/icc-x-api/crypto/AES.d.ts +2 -1
- package/icc-x-api/crypto/AES.js +37 -10
- package/icc-x-api/crypto/AES.js.map +1 -1
- package/icc-x-api/crypto/RSA.d.ts +0 -31
- package/icc-x-api/crypto/RSA.js +0 -64
- package/icc-x-api/crypto/RSA.js.map +1 -1
- package/icc-x-api/crypto/shamir.js.map +1 -1
- package/icc-x-api/filters/filters.js +7 -3
- package/icc-x-api/filters/filters.js.map +1 -1
- package/icc-x-api/icc-accesslog-x-api.js +25 -22
- package/icc-x-api/icc-accesslog-x-api.js.map +1 -1
- package/icc-x-api/icc-bekmehr-x-api.js +2 -2
- package/icc-x-api/icc-bekmehr-x-api.js.map +1 -1
- package/icc-x-api/icc-calendar-item-x-api.d.ts +2 -2
- package/icc-x-api/icc-calendar-item-x-api.js +43 -30
- package/icc-x-api/icc-calendar-item-x-api.js.map +1 -1
- package/icc-x-api/icc-contact-x-api.js +59 -45
- package/icc-x-api/icc-contact-x-api.js.map +1 -1
- package/icc-x-api/icc-crypto-x-api.d.ts +127 -54
- package/icc-x-api/icc-crypto-x-api.js +830 -393
- package/icc-x-api/icc-crypto-x-api.js.map +1 -1
- package/icc-x-api/icc-doctemplate-x-api.js +2 -2
- package/icc-x-api/icc-doctemplate-x-api.js.map +1 -1
- package/icc-x-api/icc-document-x-api.js +3 -3
- package/icc-x-api/icc-document-x-api.js.map +1 -1
- package/icc-x-api/icc-form-x-api.js +3 -3
- package/icc-x-api/icc-form-x-api.js.map +1 -1
- package/icc-x-api/icc-hcparty-x-api.js +4 -4
- package/icc-x-api/icc-hcparty-x-api.js.map +1 -1
- package/icc-x-api/icc-helement-x-api.d.ts +1 -1
- package/icc-x-api/icc-helement-x-api.js +34 -29
- package/icc-x-api/icc-helement-x-api.js.map +1 -1
- package/icc-x-api/icc-maintenance-task-x-api.js +5 -6
- package/icc-x-api/icc-maintenance-task-x-api.js.map +1 -1
- package/icc-x-api/icc-patient-x-api.d.ts +1 -1
- package/icc-x-api/icc-patient-x-api.js +65 -53
- package/icc-x-api/icc-patient-x-api.js.map +1 -1
- package/icc-x-api/index.d.ts +1 -2
- package/icc-x-api/index.js +9 -8
- package/icc-x-api/index.js.map +1 -1
- package/icc-x-api/utils/binary-utils.js +5 -5
- package/icc-x-api/utils/binary-utils.js.map +1 -1
- package/icc-x-api/utils/crypto-utils.d.ts +61 -0
- package/icc-x-api/utils/crypto-utils.js +245 -0
- package/icc-x-api/utils/crypto-utils.js.map +1 -0
- package/icc-x-api/utils/formatting-util.js +3 -3
- package/icc-x-api/utils/formatting-util.js.map +1 -1
- package/icc-x-api/utils/functional-util.d.ts +1 -0
- package/icc-x-api/utils/functional-util.js +8 -1
- package/icc-x-api/utils/functional-util.js.map +1 -1
- package/icc-x-api/utils/index.d.ts +1 -0
- package/icc-x-api/utils/index.js +6 -1
- package/icc-x-api/utils/index.js.map +1 -1
- package/index.js +5 -1
- package/index.js.map +1 -1
- package/node-compat.d.ts +1 -1
- package/node-compat.js +3 -3
- package/node-compat.js.map +1 -1
- package/package.json +2 -2
- package/icc-x-api/crypto/utils.d.ts +0 -68
- package/icc-x-api/crypto/utils.js +0 -239
- package/icc-x-api/crypto/utils.js.map +0 -1
|
@@ -12,10 +12,11 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
12
12
|
exports.IccCryptoXApi = void 0;
|
|
13
13
|
const AES_1 = require("./crypto/AES");
|
|
14
14
|
const RSA_1 = require("./crypto/RSA");
|
|
15
|
-
const utils_1 = require("./crypto/utils");
|
|
16
15
|
const shamir_1 = require("./crypto/shamir");
|
|
17
16
|
const _ = require("lodash");
|
|
17
|
+
const models_1 = require("../icc-api/model/models");
|
|
18
18
|
const binary_utils_1 = require("./utils/binary-utils");
|
|
19
|
+
const utils_1 = require("./utils");
|
|
19
20
|
class IccCryptoXApi {
|
|
20
21
|
constructor(host, headers, hcpartyBaseApi, //Init with a hcparty x api for better performances
|
|
21
22
|
patientBaseApi, deviceBaseApi, crypto = typeof window !== 'undefined' ? window.crypto : typeof self !== 'undefined' ? self.crypto : {}) {
|
|
@@ -29,6 +30,8 @@ class IccCryptoXApi {
|
|
|
29
30
|
this.keychainValidityDateLocalStoreIdPrefix = 'org.taktik.icure.ehealth.keychain-date.';
|
|
30
31
|
this.hcpPreferenceKeyEhealthCert = 'eHealthCRTCrypt';
|
|
31
32
|
this.hcpPreferenceKeyEhealthCertDate = 'eHealthCRTDate';
|
|
33
|
+
this.rsaLocalStoreIdPrefix = 'org.taktik.icure.rsa.';
|
|
34
|
+
this.rsaKeyPairs = {};
|
|
32
35
|
this.hcpartyBaseApi = hcpartyBaseApi;
|
|
33
36
|
this.patientBaseApi = patientBaseApi;
|
|
34
37
|
this.deviceBaseApi = deviceBaseApi;
|
|
@@ -36,7 +39,6 @@ class IccCryptoXApi {
|
|
|
36
39
|
this.generateKeyConcurrencyMap = {};
|
|
37
40
|
this._AES = new AES_1.AESUtils(crypto);
|
|
38
41
|
this._RSA = new RSA_1.RSAUtils(crypto);
|
|
39
|
-
this._utils = new utils_1.UtilsClass();
|
|
40
42
|
this._shamir = new shamir_1.ShamirClass(crypto);
|
|
41
43
|
}
|
|
42
44
|
get crypto() {
|
|
@@ -45,9 +47,6 @@ class IccCryptoXApi {
|
|
|
45
47
|
get shamir() {
|
|
46
48
|
return this._shamir;
|
|
47
49
|
}
|
|
48
|
-
get utils() {
|
|
49
|
-
return this._utils;
|
|
50
|
-
}
|
|
51
50
|
get RSA() {
|
|
52
51
|
return this._RSA;
|
|
53
52
|
}
|
|
@@ -66,17 +65,16 @@ class IccCryptoXApi {
|
|
|
66
65
|
* @param delegateHcPartyId The Health Care Party id
|
|
67
66
|
* @returns \{delegatorId: delegateEncryptedHcPartyKey\}
|
|
68
67
|
*/
|
|
69
|
-
|
|
70
|
-
//TODO: suggested name: getAllEncryptedHcPKeysOfDelegate
|
|
71
|
-
delegateHcPartyId) {
|
|
68
|
+
getEncryptedAesExchangeKeysForDelegate(delegateHcPartyId) {
|
|
72
69
|
return (this.hcPartyKeysRequestsCache[delegateHcPartyId] ||
|
|
73
|
-
(this.hcPartyKeysRequestsCache[delegateHcPartyId] = this.
|
|
70
|
+
(this.hcPartyKeysRequestsCache[delegateHcPartyId] = this.forceGetEncryptedAesExchangeKeysForDelegate(delegateHcPartyId)));
|
|
74
71
|
}
|
|
75
|
-
|
|
72
|
+
forceGetEncryptedAesExchangeKeysForDelegate(delegateHcPartyId) {
|
|
76
73
|
return Promise.all([
|
|
77
|
-
this.patientBaseApi.
|
|
78
|
-
this.hcpartyBaseApi.
|
|
79
|
-
|
|
74
|
+
this.patientBaseApi.getPatientAesExchangeKeysForDelegate(delegateHcPartyId).catch(() => { }),
|
|
75
|
+
this.hcpartyBaseApi.getAesExchangeKeysForDelegate(delegateHcPartyId).catch(() => { }),
|
|
76
|
+
this.deviceBaseApi.getDeviceAesExchangeKeysForDelegate(delegateHcPartyId).catch(() => { }),
|
|
77
|
+
]).then(([a, b, c]) => (Object.assign(Object.assign(Object.assign({}, a), b), c)));
|
|
80
78
|
}
|
|
81
79
|
randomUuid() {
|
|
82
80
|
return ((1e7).toString() + -1e3 + -4e3 + -8e3 + -1e11).replace(/[018]/g, (c) => (Number(c) ^ (this._crypto.getRandomValues(new Uint8Array(1))[0] & (15 >> (Number(c) / 4)))).toString(16) //Keep that inlined or you will loose the random
|
|
@@ -85,19 +83,28 @@ class IccCryptoXApi {
|
|
|
85
83
|
sha256(data) {
|
|
86
84
|
return this._crypto.subtle.digest('SHA-256', data);
|
|
87
85
|
}
|
|
88
|
-
|
|
89
|
-
return this
|
|
86
|
+
getPublicKeys() {
|
|
87
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
88
|
+
return yield Object.values(this.rsaKeyPairs).reduce((p, rsa) => __awaiter(this, void 0, void 0, function* () {
|
|
89
|
+
return (yield p).concat([(0, utils_1.jwk2spki)(yield this.RSA.exportKey(rsa.publicKey, 'jwk'))]);
|
|
90
|
+
}), Promise.resolve([]));
|
|
91
|
+
});
|
|
92
|
+
}
|
|
93
|
+
encryptShamirRSAKey(hcp, notaries, threshold) {
|
|
94
|
+
return this.loadKeyPairImported(hcp.id).then((keyPair) => this._RSA.exportKey(keyPair.privateKey, 'pkcs8').then((exportedKey) => __awaiter(this, void 0, void 0, function* () {
|
|
90
95
|
const privateKey = exportedKey;
|
|
91
96
|
const nLen = notaries.length;
|
|
92
|
-
const shares = nLen == 1 ? [privateKey] : this._shamir.share(binary_utils_1.ua2hex(privateKey), nLen, threshold || nLen).map((share) => binary_utils_1.hex2ua(share));
|
|
97
|
+
const shares = nLen == 1 ? [privateKey] : this._shamir.share((0, binary_utils_1.ua2hex)(privateKey), nLen, threshold || nLen).map((share) => (0, binary_utils_1.hex2ua)(share));
|
|
98
|
+
const publicKeys = yield this.getPublicKeys();
|
|
93
99
|
return _.reduce(notaries, (queue, notary, idx) => {
|
|
94
100
|
return queue.then((hcp) => __awaiter(this, void 0, void 0, function* () {
|
|
95
|
-
const
|
|
101
|
+
const { owner: hcParty, aesExchangeKeys } = yield this.getOrCreateHcPartyKeys(hcp, notary.id);
|
|
102
|
+
const [publicKeyIdentifier, hcPartyKeys] = Object.entries(aesExchangeKeys)[0];
|
|
96
103
|
try {
|
|
97
|
-
const importedAESHcPartyKey = yield this.decryptHcPartyKey(
|
|
104
|
+
const importedAESHcPartyKey = yield this.decryptHcPartyKey(notary.id, hcParty.id, notary.id, publicKeyIdentifier, hcPartyKeys, publicKeys);
|
|
98
105
|
const encryptedShamirPartition = yield this.AES.encrypt(importedAESHcPartyKey.key, shares[idx]);
|
|
99
106
|
hcParty.privateKeyShamirPartitions = hcParty.privateKeyShamirPartitions || {};
|
|
100
|
-
hcParty.privateKeyShamirPartitions[notary.id] = binary_utils_1.ua2hex(encryptedShamirPartition);
|
|
107
|
+
hcParty.privateKeyShamirPartitions[notary.id] = (0, binary_utils_1.ua2hex)(encryptedShamirPartition);
|
|
101
108
|
}
|
|
102
109
|
catch (e) {
|
|
103
110
|
console.log('Error during encryptedShamirRSAKey', notary.id, e);
|
|
@@ -105,7 +112,7 @@ class IccCryptoXApi {
|
|
|
105
112
|
return hcParty;
|
|
106
113
|
}));
|
|
107
114
|
}, Promise.resolve(hcp));
|
|
108
|
-
}));
|
|
115
|
+
})));
|
|
109
116
|
}
|
|
110
117
|
/* Reconstructs the hcp's private key from the notaries' shamir shares and stores it in localstorage.
|
|
111
118
|
The retrieval procedure of the shares is not designed or implemented yet. Therefore, it currently only
|
|
@@ -113,15 +120,18 @@ class IccCryptoXApi {
|
|
|
113
120
|
* @param hcp : the hcp whose key we want to reconstruct
|
|
114
121
|
* @param notaries : holders of the shamir shares
|
|
115
122
|
**/
|
|
116
|
-
|
|
123
|
+
decryptShamirRSAKey(hcp, notaries) {
|
|
117
124
|
return __awaiter(this, void 0, void 0, function* () {
|
|
118
125
|
try {
|
|
126
|
+
const publicKeys = yield this.getPublicKeys();
|
|
119
127
|
const nLen = notaries.length;
|
|
120
128
|
let decryptedPrivatedKey;
|
|
121
129
|
if (nLen == 1) {
|
|
122
|
-
const
|
|
123
|
-
const
|
|
124
|
-
|
|
130
|
+
const notaryHcPartyId = notaries[0].id;
|
|
131
|
+
const encryptedAesKeyMap = yield this.getEncryptedAesExchangeKeys(hcp, notaryHcPartyId);
|
|
132
|
+
const importedAESHcPartyKey = yield Promise.all(Object.entries(encryptedAesKeyMap).map(([idPubKey, keysMap]) => __awaiter(this, void 0, void 0, function* () { return yield this.decryptHcPartyKey(notaryHcPartyId, hcp.id, notaryHcPartyId, idPubKey, keysMap, publicKeys); })));
|
|
133
|
+
const cryptedPrivatedKey = hcp.privateKeyShamirPartitions[notaryHcPartyId];
|
|
134
|
+
decryptedPrivatedKey = (0, binary_utils_1.ua2hex)(yield this.AES.decryptSome(importedAESHcPartyKey.map((k) => k.key), (0, binary_utils_1.hex2ua)(cryptedPrivatedKey)));
|
|
125
135
|
}
|
|
126
136
|
else {
|
|
127
137
|
const decryptedShares = yield _.reduce(notaries, (queue, notary) => {
|
|
@@ -130,9 +140,10 @@ class IccCryptoXApi {
|
|
|
130
140
|
// TODO: now, we get the encrypted shares in db and decrypt them. This assumes that the
|
|
131
141
|
// the notaries' private keys are in localstorage. We should implement a way for the notaries to
|
|
132
142
|
// give hcp the decrypted shares without having to also share their private keys.
|
|
133
|
-
const
|
|
143
|
+
const encryptedAesKeyMap = yield this.getEncryptedAesExchangeKeys(hcp, notary.id);
|
|
144
|
+
const importedAESHcPartyKey = yield Promise.all(Object.entries(encryptedAesKeyMap).map(([idPubKey, keysMap]) => __awaiter(this, void 0, void 0, function* () { return yield this.decryptHcPartyKey(notary.id, hcp.id, notary.id, idPubKey, keysMap, publicKeys); })));
|
|
134
145
|
const encryptedShare = hcp.privateKeyShamirPartitions[notary.id];
|
|
135
|
-
const decryptedShamirPartition = binary_utils_1.ua2hex(yield this.AES.
|
|
146
|
+
const decryptedShamirPartition = (0, binary_utils_1.ua2hex)(yield this.AES.decryptSome(importedAESHcPartyKey.map((k) => k.key), (0, binary_utils_1.hex2ua)(encryptedShare)));
|
|
136
147
|
shares.push(decryptedShamirPartition);
|
|
137
148
|
}
|
|
138
149
|
catch (e) {
|
|
@@ -143,10 +154,10 @@ class IccCryptoXApi {
|
|
|
143
154
|
}, Promise.resolve([]));
|
|
144
155
|
decryptedPrivatedKey = this._shamir.combine(decryptedShares);
|
|
145
156
|
}
|
|
146
|
-
const importedPrivateKey = yield this.
|
|
147
|
-
const importedPublicKey = yield this.
|
|
148
|
-
const exportedKeyPair = yield this.
|
|
149
|
-
this.
|
|
157
|
+
const importedPrivateKey = yield this._RSA.importKey('pkcs8', (0, binary_utils_1.hex2ua)(decryptedPrivatedKey), ['decrypt']);
|
|
158
|
+
const importedPublicKey = yield this._RSA.importKey('spki', (0, binary_utils_1.hex2ua)(hcp.publicKey), ['encrypt']);
|
|
159
|
+
const exportedKeyPair = yield this._RSA.exportKeys({ publicKey: importedPublicKey, privateKey: importedPrivateKey }, 'jwk', 'jwk');
|
|
160
|
+
this.storeKeyPair(hcp.id, exportedKeyPair);
|
|
150
161
|
}
|
|
151
162
|
catch (e) {
|
|
152
163
|
console.log('Cannot decrypt shamir RSA key');
|
|
@@ -158,63 +169,123 @@ class IccCryptoXApi {
|
|
|
158
169
|
*
|
|
159
170
|
* If the decrypted key exists in the cache, retrieves it from there.
|
|
160
171
|
* Otherwise, decrypts it using the RSA key of the delegator or delegate (depending on the value of `encryptedForDelegator`)
|
|
161
|
-
*
|
|
162
|
-
* @param
|
|
163
|
-
* @param
|
|
164
|
-
* @param
|
|
172
|
+
*
|
|
173
|
+
* @param loggedHcPartyId The logged DataOwner id or the id of his parent
|
|
174
|
+
* @param delegatorId The id of Delegator eg, the DataOwner where this AES exchange key is stored
|
|
175
|
+
* @param delegateHcPartyId The id of the delegate : the data owner for whom this aes exchange key has been created
|
|
176
|
+
* @param publicKey The public key of the delegator : A unique aes exchange key is created for each public key of the delegator. This is the public key corresponding to the encryptedHcPartyKeys
|
|
177
|
+
* @param encryptedHcPartyKeys The encryptedHcPartyKeys to be decrypted
|
|
178
|
+
* @param publicKeys The public keys for which we might have a private key
|
|
179
|
+
*
|
|
165
180
|
* @returns - **delegatorId** the input param `delegatorId`
|
|
166
181
|
* - **key** the decrypted `encryptedHcPartyKey`
|
|
167
182
|
*/
|
|
168
|
-
decryptHcPartyKey(delegatorId, delegateHcPartyId,
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
const
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
183
|
+
decryptHcPartyKey(loggedHcPartyId, delegatorId, delegateHcPartyId, publicKey, encryptedHcPartyKeys, publicKeys) {
|
|
184
|
+
var _a;
|
|
185
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
186
|
+
if (!publicKeys.length) {
|
|
187
|
+
const reason = `Cannot decrypt RSA encrypted AES HcPartyKey from ${delegatorId} to ${delegateHcPartyId}: no public key`;
|
|
188
|
+
console.warn(reason);
|
|
189
|
+
throw new Error(reason);
|
|
190
|
+
}
|
|
191
|
+
const cacheKey = `${delegateHcPartyId}|${publicKey.slice(-32)}|${delegatorId}`;
|
|
192
|
+
const res = this.hcPartyKeysCache[cacheKey];
|
|
193
|
+
if (res) {
|
|
194
|
+
return res;
|
|
195
|
+
}
|
|
196
|
+
const result = yield publicKeys.reduce((res, pk) => __awaiter(this, void 0, void 0, function* () {
|
|
197
|
+
var _b;
|
|
198
|
+
const delegatorAndKeys = yield res;
|
|
199
|
+
if (delegatorAndKeys) {
|
|
200
|
+
return delegatorAndKeys;
|
|
201
|
+
}
|
|
202
|
+
const fingerprint = pk.slice(-32);
|
|
203
|
+
const keyPair = (_b = this.rsaKeyPairs[fingerprint]) !== null && _b !== void 0 ? _b : (yield this.cacheKeyPair(this.loadKeyPairNotImported(loggedHcPartyId, fingerprint)));
|
|
204
|
+
if (!keyPair) {
|
|
205
|
+
return;
|
|
206
|
+
}
|
|
207
|
+
const encryptedHcPartyKey = encryptedHcPartyKeys[fingerprint];
|
|
208
|
+
if (!encryptedHcPartyKey) {
|
|
209
|
+
return;
|
|
210
|
+
}
|
|
211
|
+
try {
|
|
212
|
+
const decryptedAesExchangeKey = yield this._RSA.decrypt(keyPair.privateKey, (0, binary_utils_1.hex2ua)(encryptedHcPartyKey));
|
|
213
|
+
const importedAesExchangeKey = yield this._AES.importKey('raw', decryptedAesExchangeKey);
|
|
214
|
+
return (this.hcPartyKeysCache[cacheKey] = {
|
|
215
|
+
delegatorId: delegatorId,
|
|
216
|
+
key: importedAesExchangeKey,
|
|
217
|
+
rawKey: (0, binary_utils_1.ua2hex)(new Uint8Array(decryptedAesExchangeKey)),
|
|
218
|
+
});
|
|
219
|
+
}
|
|
220
|
+
catch (e) {
|
|
221
|
+
const reason = `Cannot decrypt RSA encrypted AES HcPartyKey from ${delegatorId} to ${delegateHcPartyId} for pubKey ${fingerprint}: impossible to decrypt`;
|
|
222
|
+
console.log(reason);
|
|
223
|
+
}
|
|
224
|
+
}), Promise.resolve());
|
|
225
|
+
const availablePublicKeys = publicKeys.filter((pk) => this.rsaKeyPairs[pk.slice(-32)]);
|
|
226
|
+
if (!result) {
|
|
227
|
+
//Try to find another key from the transfer keys
|
|
228
|
+
const hcp = (yield this.getDataOwner(loggedHcPartyId));
|
|
229
|
+
const candidates = Object.entries((_a = hcp.dataOwner.transferKeys) !== null && _a !== void 0 ? _a : {})
|
|
230
|
+
.filter(([fp, _]) => availablePublicKeys.some((pk) => pk.slice(-32) === fp.slice(-32))) // only keep keys that we will be able to decrypt
|
|
231
|
+
.flatMap(([pk, keys]) => Object.entries(keys).map(([k, v]) => [pk, k, v]))
|
|
232
|
+
.filter(([_, k]) => !publicKeys.some((apk) => apk.slice(-32) === k.slice(-32)));
|
|
233
|
+
if (candidates.length) {
|
|
234
|
+
const newPublicKeys = yield candidates.reduce((p, [decryptionKeyFingerprint, privateKeyFingerprint, encryptedPrivateKey]) => __awaiter(this, void 0, void 0, function* () {
|
|
235
|
+
var _c, _d;
|
|
236
|
+
const newKeys = yield p;
|
|
237
|
+
const aesExchangeKeys = (_c = Object.entries(hcp.dataOwner.aesExchangeKeys).find(([fp, _]) => fp.slice(-32) === privateKeyFingerprint.slice(-32))) === null || _c === void 0 ? void 0 : _c[1][loggedHcPartyId];
|
|
238
|
+
if (aesExchangeKeys) {
|
|
239
|
+
const encryptedAesExchangeKey = (_d = Object.entries(aesExchangeKeys).find(([fp, _]) => fp.slice(-32) === decryptionKeyFingerprint.slice(-32))) === null || _d === void 0 ? void 0 : _d[1];
|
|
240
|
+
if (encryptedAesExchangeKey) {
|
|
241
|
+
const keyPair = this.rsaKeyPairs[decryptionKeyFingerprint.slice(-32)];
|
|
242
|
+
if (!keyPair) {
|
|
243
|
+
return newKeys;
|
|
244
|
+
}
|
|
245
|
+
const decryptedAesExchangeKey = yield this._RSA.decrypt(keyPair.privateKey, (0, binary_utils_1.hex2ua)(encryptedAesExchangeKey));
|
|
246
|
+
const importedAesExchangeKey = yield this._AES.importKey('raw', decryptedAesExchangeKey);
|
|
247
|
+
const decryptedPrivateKey = yield this._AES.decrypt(importedAesExchangeKey, (0, binary_utils_1.hex2ua)(encryptedPrivateKey));
|
|
248
|
+
const newPublicKey = Object.keys(hcp.dataOwner.aesExchangeKeys).find((fp) => fp.slice(-32) === privateKeyFingerprint.slice(-32));
|
|
249
|
+
yield this.cacheKeyPair({
|
|
250
|
+
publicKey: (0, utils_1.spkiToJwk)((0, binary_utils_1.hex2ua)(newPublicKey)),
|
|
251
|
+
privateKey: (0, utils_1.pkcs8ToJwk)(decryptedPrivateKey),
|
|
252
|
+
});
|
|
253
|
+
return newKeys.concat([newPublicKey]);
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
return newKeys;
|
|
257
|
+
}), Promise.resolve([]));
|
|
258
|
+
if (newPublicKeys.length) {
|
|
259
|
+
return yield this.decryptHcPartyKey(loggedHcPartyId, delegatorId, delegateHcPartyId, publicKey, encryptedHcPartyKeys, newPublicKeys);
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
const reason = `Cannot decrypt RSA encrypted AES HcPartyKey from ${delegatorId} to ${delegateHcPartyId}: impossible to decrypt. No private key was found or could be used to decrypt the aes exchange keys`;
|
|
263
|
+
console.log(reason);
|
|
264
|
+
throw new Error(reason);
|
|
265
|
+
}
|
|
266
|
+
return result;
|
|
267
|
+
});
|
|
196
268
|
}
|
|
197
269
|
/**
|
|
198
270
|
* Cache the RSA private/public key pair for the HcP with the given id `hcPartyKeyOwner`
|
|
199
271
|
*/
|
|
200
|
-
cacheKeyPair(keyPairInJwk
|
|
201
|
-
if (!keyPairInJwk) {
|
|
202
|
-
throw 'No RSA private key for Healthcare party(' + hcPartyKeyOwner + ').';
|
|
203
|
-
}
|
|
272
|
+
cacheKeyPair(keyPairInJwk) {
|
|
204
273
|
return this._RSA.importKeyPair('jwk', keyPairInJwk.privateKey, 'jwk', keyPairInJwk.publicKey).then((importedKeyPair) => {
|
|
205
|
-
|
|
274
|
+
const pk = (0, utils_1.jwk2spki)(keyPairInJwk.publicKey);
|
|
275
|
+
return (this.rsaKeyPairs[pk.slice(-32)] = importedKeyPair);
|
|
206
276
|
});
|
|
207
277
|
}
|
|
208
278
|
/**
|
|
209
|
-
* Gets the secret ID (SFKs) that should be used in the prescribed context (
|
|
210
|
-
* @param parent :
|
|
211
|
-
* @param hcpartyId :
|
|
212
|
-
* @param confidential :
|
|
279
|
+
* Gets the secret ID (SFKs) that should be used in the prescribed context (confidential or not) from decrypted SPKs of the given `parent`, decrypted by the HcP with the given `hcpartyId` AND by its HcP parents
|
|
280
|
+
* @param parent :the object of which delegations (SPKs) to decrypt
|
|
281
|
+
* @param hcpartyId :the id of the delegate HcP
|
|
282
|
+
* @param confidential :weather the key is going to be used for a confidential piece or data or not
|
|
213
283
|
* @returns - **extractedKeys** array containing secret IDs (SFKs) from decrypted SPKs, from both given HcP and its parents ; can contain duplicates
|
|
214
284
|
* - **hcpartyId** the given `hcpartyId` OR, if a parent exist, the HcP id of the top parent in the hierarchy (even if that parent has no delegations)
|
|
215
285
|
*/
|
|
216
286
|
extractPreferredSfk(parent, hcpartyId, confidential) {
|
|
217
|
-
return this
|
|
287
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
288
|
+
const secretForeignKeys = yield this.extractSFKsHierarchyFromDelegations(parent, hcpartyId);
|
|
218
289
|
const keys = secretForeignKeys
|
|
219
290
|
.filter(({ extractedKeys }) => extractedKeys.length > 0)
|
|
220
291
|
.filter((x, idx) => (confidential ? x.hcpartyId === hcpartyId : idx === 0))[0];
|
|
@@ -222,7 +293,7 @@ class IccCryptoXApi {
|
|
|
222
293
|
(confidential
|
|
223
294
|
? keys.extractedKeys.find((k) => !secretForeignKeys.some(({ extractedKeys, hcpartyId: parentHcpId }) => hcpartyId !== parentHcpId && extractedKeys.includes(k)))
|
|
224
295
|
: keys.extractedKeys[0])) ||
|
|
225
|
-
|
|
296
|
+
undefined);
|
|
226
297
|
});
|
|
227
298
|
}
|
|
228
299
|
/**
|
|
@@ -238,34 +309,39 @@ class IccCryptoXApi {
|
|
|
238
309
|
* @returns - **delegatorId** : the id of the delegator HcP that shares the **key** with the `delegateHcPartyId`
|
|
239
310
|
* - **key** the decrypted HcPartyKey, shared between **delegatorId** and `delegateHcPartyId`
|
|
240
311
|
*/
|
|
241
|
-
decryptAndImportAesHcPartyKeysForDelegators(
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
312
|
+
decryptAndImportAesHcPartyKeysForDelegators(delegatorsHcPartyIdsSet, delegateHcPartyId, minCacheDurationInSeconds = 60) {
|
|
313
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
314
|
+
const aesExchangeKeys = yield (this.hcPartyKeysRequestsCache[delegateHcPartyId] ||
|
|
315
|
+
(this.hcPartyKeysRequestsCache[delegateHcPartyId] = this.getEncryptedAesExchangeKeysForDelegate(delegateHcPartyId))).then((delegatorIdsWithDelegateEncryptedHcPartyKeys) => __awaiter(this, void 0, void 0, function* () {
|
|
316
|
+
// [key: delegatorId] = delegateEncryptedHcPartyKey
|
|
317
|
+
// For each delegatorId, obtain the AES key (decrypted HcParty Key) shared with the delegate, decrypted by the delegate
|
|
318
|
+
return (yield Promise.all(delegatorsHcPartyIdsSet.map((delegatorId) => __awaiter(this, void 0, void 0, function* () {
|
|
319
|
+
const encryptedHcPartyKeysForPubKeyFingerprint = delegatorIdsWithDelegateEncryptedHcPartyKeys[delegatorId];
|
|
320
|
+
if (!encryptedHcPartyKeysForPubKeyFingerprint) {
|
|
321
|
+
return [];
|
|
322
|
+
}
|
|
323
|
+
const decryptedKeys = yield Promise.all(Object.entries(encryptedHcPartyKeysForPubKeyFingerprint).map(([delegatorPubKeyFinerprint, encryptedAesExchangeKeys]) => __awaiter(this, void 0, void 0, function* () {
|
|
324
|
+
try {
|
|
325
|
+
return yield this.decryptHcPartyKey(delegateHcPartyId, delegatorId, delegateHcPartyId, delegatorPubKeyFinerprint, encryptedAesExchangeKeys, Object.keys(this.rsaKeyPairs));
|
|
326
|
+
}
|
|
327
|
+
catch (e) {
|
|
328
|
+
console.log(`failed to decrypt hcPartyKey from ${delegatorId} to ${delegateHcPartyId}`);
|
|
329
|
+
return;
|
|
330
|
+
}
|
|
331
|
+
})));
|
|
332
|
+
return decryptedKeys.filter((x) => !!x);
|
|
333
|
+
})))).reduce((acc, x) => [...acc, ...x], []);
|
|
334
|
+
}));
|
|
335
|
+
if (aesExchangeKeys.length > 0) {
|
|
336
|
+
return aesExchangeKeys;
|
|
337
|
+
}
|
|
338
|
+
const nowTimestamp = +new Date();
|
|
339
|
+
if (!this.cacheLastDeletionTimestamp || (nowTimestamp - this.cacheLastDeletionTimestamp) / 1000 >= minCacheDurationInSeconds) {
|
|
340
|
+
delete this.hcPartyKeysRequestsCache[delegateHcPartyId];
|
|
341
|
+
this.cacheLastDeletionTimestamp = nowTimestamp;
|
|
342
|
+
return this.decryptAndImportAesHcPartyKeysForDelegators(delegatorsHcPartyIdsSet, delegateHcPartyId, minCacheDurationInSeconds);
|
|
343
|
+
}
|
|
344
|
+
return [];
|
|
269
345
|
});
|
|
270
346
|
}
|
|
271
347
|
/**
|
|
@@ -278,77 +354,99 @@ class IccCryptoXApi {
|
|
|
278
354
|
* 3. Decrypt's delegators' keys and returns them.
|
|
279
355
|
*
|
|
280
356
|
* @param dataOwnerId : the id of the delegate HCP
|
|
281
|
-
* @param delegations :
|
|
357
|
+
* @param delegations : delegations (can be SPKs, CFKs, EKs) for all delegates
|
|
282
358
|
* @param fallbackOnParent default true; use parent's healthCarePartyId in case there's no delegation for the `healthcarePartyId`
|
|
283
359
|
* @returns - **delegatorId** : the id of the delegator HcP that shares the **key** with the `healthcarePartyId`
|
|
284
360
|
* - **key** the decrypted HcPartyKey, shared between **delegatorId** and `healthcarePartyId`
|
|
285
361
|
*/
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
362
|
+
getDecryptedAesExchangeKeysOfDelegateAndParentsFromGenericDelegations(dataOwnerId, delegations, fallbackOnParent = true) {
|
|
363
|
+
var _a;
|
|
364
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
365
|
+
const delegationsArray = (_a = delegations[dataOwnerId]) !== null && _a !== void 0 ? _a : [];
|
|
366
|
+
if (!delegationsArray.length && fallbackOnParent) {
|
|
367
|
+
const { dataOwner: hcp } = yield this.getDataOwner(dataOwnerId);
|
|
368
|
+
return hcp.parentId
|
|
369
|
+
? yield this.getDecryptedAesExchangeKeysOfDelegateAndParentsFromGenericDelegations(hcp.parentId, delegations)
|
|
370
|
+
: [];
|
|
371
|
+
}
|
|
372
|
+
else {
|
|
373
|
+
const delegatorIds = new Set();
|
|
374
|
+
delegationsArray.forEach((del) => delegatorIds.add(del.owner));
|
|
375
|
+
return this.decryptAndImportAesHcPartyKeysForDelegators(Array.from(delegatorIds), dataOwnerId);
|
|
376
|
+
}
|
|
377
|
+
});
|
|
300
378
|
}
|
|
301
379
|
/**
|
|
302
|
-
*
|
|
380
|
+
* Retrieve the owner HealthCareParty key and use it to encrypt
|
|
303
381
|
* both the delegations (createdObject.id) and the cryptedForeignKeys
|
|
304
382
|
* (parentObject.id), and returns them in an object.
|
|
305
383
|
*/
|
|
306
384
|
initObjectDelegations(createdObject, parentObject, ownerId, secretForeignKeyOfParent) {
|
|
307
|
-
this
|
|
308
|
-
|
|
309
|
-
this.throwDetailedExceptionForInvalidParameter('
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
return this.
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
.then((encryptedDelegationAndSecretForeignKey) => ({
|
|
323
|
-
delegations: _.fromPairs([
|
|
324
|
-
[
|
|
325
|
-
ownerId,
|
|
326
|
-
[
|
|
327
|
-
{
|
|
328
|
-
owner: ownerId,
|
|
329
|
-
delegatedTo: ownerId,
|
|
330
|
-
key: binary_utils_1.ua2hex(encryptedDelegationAndSecretForeignKey[0]),
|
|
331
|
-
},
|
|
332
|
-
],
|
|
333
|
-
],
|
|
334
|
-
]),
|
|
335
|
-
cryptedForeignKeys: (encryptedDelegationAndSecretForeignKey[1] &&
|
|
336
|
-
_.fromPairs([
|
|
337
|
-
[
|
|
338
|
-
ownerId,
|
|
385
|
+
return __awaiter(this, arguments, void 0, function* () {
|
|
386
|
+
const publicKeys = yield this.getPublicKeys();
|
|
387
|
+
this.throwDetailedExceptionForInvalidParameter('createdObject.id', createdObject.id, 'initObjectDelegations', arguments);
|
|
388
|
+
if (parentObject)
|
|
389
|
+
this.throwDetailedExceptionForInvalidParameter('parentObject.id', parentObject.id, 'initObjectDelegations', arguments);
|
|
390
|
+
const secretId = this.randomUuid();
|
|
391
|
+
return this.getDataOwner(ownerId).then(({ dataOwner: owner }) => __awaiter(this, void 0, void 0, function* () {
|
|
392
|
+
const { owner: modifiedOwner, aesExchangeKeys } = yield this.getOrCreateHcPartyKeys(owner, ownerId);
|
|
393
|
+
const importedAESHcPartyKey = yield this.decryptAnyAesExchangeKeyForOwner(aesExchangeKeys, ownerId, ownerId, ownerId, publicKeys);
|
|
394
|
+
const encryptedDelegation = yield this._AES.encrypt(importedAESHcPartyKey.key, (0, binary_utils_1.string2ua)(createdObject.id + ':' + secretId).buffer, importedAESHcPartyKey.rawKey);
|
|
395
|
+
const encryptedSecretForeignKey = parentObject &&
|
|
396
|
+
this._AES.encrypt(importedAESHcPartyKey.key, (0, binary_utils_1.string2ua)(createdObject.id + ':' + parentObject.id).buffer, importedAESHcPartyKey.rawKey);
|
|
397
|
+
return {
|
|
398
|
+
owner: modifiedOwner,
|
|
399
|
+
delegations: _.fromPairs([
|
|
339
400
|
[
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
401
|
+
ownerId,
|
|
402
|
+
[
|
|
403
|
+
{
|
|
404
|
+
owner: ownerId,
|
|
405
|
+
delegatedTo: ownerId,
|
|
406
|
+
key: (0, binary_utils_1.ua2hex)(encryptedDelegation),
|
|
407
|
+
},
|
|
408
|
+
],
|
|
345
409
|
],
|
|
346
|
-
],
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
410
|
+
]),
|
|
411
|
+
cryptedForeignKeys: (encryptedSecretForeignKey &&
|
|
412
|
+
_.fromPairs([
|
|
413
|
+
[
|
|
414
|
+
ownerId,
|
|
415
|
+
[
|
|
416
|
+
{
|
|
417
|
+
owner: ownerId,
|
|
418
|
+
delegatedTo: ownerId,
|
|
419
|
+
key: (0, binary_utils_1.ua2hex)(encryptedSecretForeignKey),
|
|
420
|
+
},
|
|
421
|
+
],
|
|
422
|
+
],
|
|
423
|
+
])) ||
|
|
424
|
+
{},
|
|
425
|
+
secretForeignKeys: (secretForeignKeyOfParent && [secretForeignKeyOfParent]) || [],
|
|
426
|
+
secretId: secretId,
|
|
427
|
+
};
|
|
428
|
+
}));
|
|
429
|
+
});
|
|
430
|
+
}
|
|
431
|
+
decryptAnyAesExchangeKeyForOwner(aesExchangeKeys, loggedHcPartyId, delegatorId, delegateHcPartyId, publicKeys) {
|
|
432
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
433
|
+
const importedAESHcPartyKey = yield Object.entries(aesExchangeKeys).reduce((acc, [publicKeyIdentifier, hcPartyKeys]) => __awaiter(this, void 0, void 0, function* () {
|
|
434
|
+
const accValue = yield acc;
|
|
435
|
+
if (accValue) {
|
|
436
|
+
return accValue;
|
|
437
|
+
}
|
|
438
|
+
try {
|
|
439
|
+
return yield this.decryptHcPartyKey(loggedHcPartyId, delegatorId, delegateHcPartyId, publicKeyIdentifier, hcPartyKeys, publicKeys);
|
|
440
|
+
}
|
|
441
|
+
catch (e) {
|
|
442
|
+
return undefined;
|
|
443
|
+
}
|
|
444
|
+
}), Promise.resolve());
|
|
445
|
+
if (!importedAESHcPartyKey) {
|
|
446
|
+
throw new Error(`No hcParty key can be decrypted from ${delegatorId} to ${delegateHcPartyId} using currently available private keys`);
|
|
447
|
+
}
|
|
448
|
+
return importedAESHcPartyKey;
|
|
449
|
+
});
|
|
352
450
|
}
|
|
353
451
|
/**
|
|
354
452
|
* Gets updated instances of SPKs and CKFs for the child object `modifiedObject`.
|
|
@@ -377,32 +475,36 @@ class IccCryptoXApi {
|
|
|
377
475
|
modifiedObject, parentObject, ownerId, delegateId, secretIdOfModifiedObject) {
|
|
378
476
|
this.throwDetailedExceptionForInvalidParameter('modifiedObject.id', modifiedObject.id, 'extendedDelegationsAndCryptedForeignKeys', arguments); //modifiedObject should never be null
|
|
379
477
|
if (parentObject)
|
|
380
|
-
this.throwDetailedExceptionForInvalidParameter('parentObject.id', parentObject.id, 'extendedDelegationsAndCryptedForeignKeys', arguments);
|
|
478
|
+
this.throwDetailedExceptionForInvalidParameter('parentObject.id', parentObject === null || parentObject === void 0 ? void 0 : parentObject.id, 'extendedDelegationsAndCryptedForeignKeys', arguments);
|
|
381
479
|
this.throwDetailedExceptionForInvalidParameter('secretIdOfModifiedObject', secretIdOfModifiedObject, 'extendedDelegationsAndCryptedForeignKeys', arguments);
|
|
382
480
|
return this.getDataOwner(ownerId)
|
|
383
|
-
.then(({ dataOwner: owner }) => this
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
:
|
|
404
|
-
|
|
405
|
-
|
|
481
|
+
.then(({ dataOwner: owner }) => __awaiter(this, void 0, void 0, function* () {
|
|
482
|
+
const publicKeys = yield this.getPublicKeys();
|
|
483
|
+
const { owner: modifiedOwner, aesExchangeKeys } = yield this.getOrCreateHcPartyKeys(owner, delegateId);
|
|
484
|
+
const importedAESHcPartyKey = yield this.decryptAnyAesExchangeKeyForOwner(aesExchangeKeys, ownerId, delegateId, ownerId, publicKeys);
|
|
485
|
+
modifiedObject = (modifiedObject === null || modifiedObject === void 0 ? void 0 : modifiedObject.id) === owner.id ? modifiedOwner : modifiedObject;
|
|
486
|
+
return {
|
|
487
|
+
previousDecryptedDelegations: yield Promise.all(((modifiedObject.delegations || {})[delegateId] || []).map((d) => (d.key &&
|
|
488
|
+
d.owner === ownerId &&
|
|
489
|
+
this._AES.decrypt(importedAESHcPartyKey.key, (0, binary_utils_1.hex2ua)(d.key), importedAESHcPartyKey.rawKey).catch(() => {
|
|
490
|
+
console.log(`Cannot decrypt delegation from ${d.owner} to ${d.delegatedTo} for object with id ${modifiedObject.id}:`, modifiedObject);
|
|
491
|
+
return Promise.resolve();
|
|
492
|
+
})) ||
|
|
493
|
+
Promise.resolve())),
|
|
494
|
+
previousDecryptedCryptedForeignKeys: yield Promise.all(((modifiedObject.cryptedForeignKeys || {})[delegateId] || []).map((d) => (d.key &&
|
|
495
|
+
d.owner === ownerId &&
|
|
496
|
+
this._AES.decrypt(importedAESHcPartyKey.key, (0, binary_utils_1.hex2ua)(d.key), importedAESHcPartyKey.rawKey).catch(() => {
|
|
497
|
+
console.log(`Cannot decrypt cryptedForeignKeys from ${d.owner} to ${d.delegatedTo} for object with id ${modifiedObject.id}:`, modifiedObject);
|
|
498
|
+
return Promise.resolve();
|
|
499
|
+
})) ||
|
|
500
|
+
Promise.resolve())),
|
|
501
|
+
cryptedDelegation: yield this._AES.encrypt(importedAESHcPartyKey.key, (0, binary_utils_1.string2ua)(modifiedObject.id + ':' + secretIdOfModifiedObject).buffer, importedAESHcPartyKey.rawKey),
|
|
502
|
+
cryptedForeignKey: parentObject
|
|
503
|
+
? yield this._AES.encrypt(importedAESHcPartyKey.key, (0, binary_utils_1.string2ua)(modifiedObject.id + ':' + parentObject.id).buffer, importedAESHcPartyKey.rawKey)
|
|
504
|
+
: undefined,
|
|
505
|
+
};
|
|
506
|
+
}))
|
|
507
|
+
.then(({ previousDecryptedDelegations, previousDecryptedCryptedForeignKeys, cryptedDelegation, cryptedForeignKey }) => {
|
|
406
508
|
//try to limit the extent of the modifications to the delegations by preserving the redundant delegation already present and removing duplicates
|
|
407
509
|
//For delegate delegateId, we create:
|
|
408
510
|
// 1. an array of objects { d : {owner,delegatedTo,encrypted(key)}} with one object for each existing delegation and the new key concatenated
|
|
@@ -410,36 +512,42 @@ class IccCryptoXApi {
|
|
|
410
512
|
// We merge them to get one array of objects: { d: {owner,delegatedTo,encrypted(key)}, k: decrypted(key)}
|
|
411
513
|
const delegationCryptedDecrypted = _.merge(((modifiedObject.delegations || {})[delegateId] || []).map((d) => ({
|
|
412
514
|
d,
|
|
413
|
-
})), (previousDecryptedDelegations || []).map((dd) => (dd ? binary_utils_1.ua2string(dd) : null)).map((k) => ({ k })))
|
|
515
|
+
})), (previousDecryptedDelegations || []).map((dd) => (dd ? (0, binary_utils_1.ua2string)(dd) : null)).map((k) => ({ k })))
|
|
414
516
|
.filter(({ d, k }) => !!k || d.owner !== ownerId) //Only keep the ones created by us that can still be decrypted
|
|
415
|
-
.map(({ d, k }) => ({
|
|
517
|
+
.map(({ d, k }) => ({
|
|
518
|
+
d,
|
|
519
|
+
k: k || this.randomUuid(),
|
|
520
|
+
})) // Use some unique id that ensures the delegation not created by us are going to be held
|
|
416
521
|
.concat([
|
|
417
522
|
{
|
|
418
523
|
d: {
|
|
419
524
|
owner: ownerId,
|
|
420
525
|
delegatedTo: delegateId,
|
|
421
|
-
key: binary_utils_1.ua2hex(cryptedDelegation),
|
|
526
|
+
key: (0, binary_utils_1.ua2hex)(cryptedDelegation),
|
|
422
527
|
},
|
|
423
528
|
k: modifiedObject.id + ':' + secretIdOfModifiedObject,
|
|
424
529
|
},
|
|
425
530
|
]);
|
|
426
|
-
const allDelegations = _.cloneDeep(modifiedObject.delegations);
|
|
531
|
+
const allDelegations = _.cloneDeep(modifiedObject.delegations || {});
|
|
427
532
|
//Only keep one version of the decrypted key
|
|
428
533
|
allDelegations[delegateId] = _.uniqBy(delegationCryptedDecrypted, (x) => x.k).map((x) => x.d);
|
|
429
534
|
const cryptedForeignKeysCryptedDecrypted = _.merge(((modifiedObject.cryptedForeignKeys || {})[delegateId] || []).map((d) => ({
|
|
430
535
|
d,
|
|
431
|
-
})), (previousDecryptedCryptedForeignKeys || []).map((dd) => (dd ? binary_utils_1.ua2string(dd) : null)).map((k) => ({ k })))
|
|
536
|
+
})), (previousDecryptedCryptedForeignKeys || []).map((dd) => (dd ? (0, binary_utils_1.ua2string)(dd) : null)).map((k) => ({ k })))
|
|
432
537
|
.filter(({ d, k }) => !!k || d.owner !== ownerId) //Only keep the ones created by us that can still be decrypted
|
|
433
|
-
.map(({ d, k }) => ({
|
|
538
|
+
.map(({ d, k }) => ({
|
|
539
|
+
d,
|
|
540
|
+
k: k || this.randomUuid(),
|
|
541
|
+
})) // Use some unique id that ensures the delegation not created by us are going to be held
|
|
434
542
|
.concat(cryptedForeignKey
|
|
435
543
|
? [
|
|
436
544
|
{
|
|
437
545
|
d: {
|
|
438
546
|
owner: ownerId,
|
|
439
547
|
delegatedTo: delegateId,
|
|
440
|
-
key: binary_utils_1.ua2hex(cryptedForeignKey),
|
|
548
|
+
key: (0, binary_utils_1.ua2hex)(cryptedForeignKey),
|
|
441
549
|
},
|
|
442
|
-
k: modifiedObject.id + ':' + parentObject.id,
|
|
550
|
+
k: modifiedObject.id + ':' + (parentObject === null || parentObject === void 0 ? void 0 : parentObject.id),
|
|
443
551
|
},
|
|
444
552
|
]
|
|
445
553
|
: []);
|
|
@@ -448,16 +556,38 @@ class IccCryptoXApi {
|
|
|
448
556
|
allCryptedForeignKeys[delegateId] = _.uniqBy(cryptedForeignKeysCryptedDecrypted, (x) => x.k).map((x) => x.d);
|
|
449
557
|
}
|
|
450
558
|
return {
|
|
559
|
+
modifiedObject,
|
|
451
560
|
delegations: allDelegations,
|
|
452
561
|
cryptedForeignKeys: allCryptedForeignKeys,
|
|
453
562
|
secretId: secretIdOfModifiedObject,
|
|
454
563
|
};
|
|
455
564
|
});
|
|
456
565
|
}
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
566
|
+
getEncryptedAesExchangeKeys(owner, delegateId) {
|
|
567
|
+
var _a, _b;
|
|
568
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
569
|
+
const publicKeys = yield this.getPublicKeys();
|
|
570
|
+
const mapOfAesExchangeKeys = Object.entries((_a = owner.aesExchangeKeys) !== null && _a !== void 0 ? _a : {})
|
|
571
|
+
.filter((e) => e[1][delegateId] && Object.keys(e[1][delegateId]).some((k1) => publicKeys.some((pk) => pk.endsWith(k1))))
|
|
572
|
+
.reduce((map, e) => {
|
|
573
|
+
const candidates = Object.entries(e[1][delegateId]);
|
|
574
|
+
const [publicKeyFingerprint, encryptedAesExchangeKey] = candidates[candidates.findIndex(([k, v]) => publicKeys.some((pk) => pk.endsWith(k)))];
|
|
575
|
+
return Object.assign(Object.assign({}, map), { [e[0]]: { [publicKeyFingerprint]: encryptedAesExchangeKey } });
|
|
576
|
+
}, {});
|
|
577
|
+
return !owner.publicKey || mapOfAesExchangeKeys[owner.publicKey] || !((_b = owner.hcPartyKeys) === null || _b === void 0 ? void 0 : _b[delegateId])
|
|
578
|
+
? mapOfAesExchangeKeys
|
|
579
|
+
: Object.assign(Object.assign({}, mapOfAesExchangeKeys), { [owner.publicKey]: { [owner.publicKey.slice(-32)]: owner.hcPartyKeys[delegateId][0] } });
|
|
580
|
+
});
|
|
581
|
+
}
|
|
582
|
+
getOrCreateHcPartyKeys(owner, delegateId) {
|
|
583
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
584
|
+
const aesExchangeKeys = yield this.getEncryptedAesExchangeKeys(owner, delegateId);
|
|
585
|
+
if (Object.keys(aesExchangeKeys).length) {
|
|
586
|
+
return { owner, aesExchangeKeys };
|
|
587
|
+
}
|
|
588
|
+
const modifiedOwner = yield this.generateKeyForDelegate(owner.id, delegateId);
|
|
589
|
+
return { owner: modifiedOwner, aesExchangeKeys: yield this.getEncryptedAesExchangeKeys(modifiedOwner, delegateId) };
|
|
590
|
+
});
|
|
461
591
|
}
|
|
462
592
|
/**
|
|
463
593
|
* Retrieve the owners HealthCareParty key, decrypt it, and
|
|
@@ -469,24 +599,26 @@ class IccCryptoXApi {
|
|
|
469
599
|
initEncryptionKeys(createdObject, ownerId) {
|
|
470
600
|
this.throwDetailedExceptionForInvalidParameter('createdObject.id', createdObject.id, 'initEncryptionKeys', arguments);
|
|
471
601
|
const secretId = this.randomUuid();
|
|
472
|
-
return this.getDataOwner(ownerId)
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
.
|
|
477
|
-
|
|
478
|
-
[
|
|
479
|
-
ownerId,
|
|
602
|
+
return this.getDataOwner(ownerId).then(({ dataOwner: owner }) => __awaiter(this, void 0, void 0, function* () {
|
|
603
|
+
const publicKeys = yield this.getPublicKeys();
|
|
604
|
+
const { owner: modifiedOwner, aesExchangeKeys } = yield this.getOrCreateHcPartyKeys(owner, ownerId);
|
|
605
|
+
const importedAESHcPartyKey = yield this.decryptAnyAesExchangeKeyForOwner(aesExchangeKeys, ownerId, ownerId, ownerId, publicKeys);
|
|
606
|
+
const encryptedEncryptionKeys = yield this._AES.encrypt(importedAESHcPartyKey.key, (0, binary_utils_1.string2ua)(createdObject.id + ':' + secretId), importedAESHcPartyKey.rawKey);
|
|
607
|
+
return {
|
|
608
|
+
encryptionKeys: _.fromPairs([
|
|
480
609
|
[
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
610
|
+
ownerId,
|
|
611
|
+
[
|
|
612
|
+
{
|
|
613
|
+
owner: ownerId,
|
|
614
|
+
delegatedTo: ownerId,
|
|
615
|
+
key: (0, binary_utils_1.ua2hex)(encryptedEncryptionKeys),
|
|
616
|
+
},
|
|
617
|
+
],
|
|
486
618
|
],
|
|
487
|
-
],
|
|
488
|
-
|
|
489
|
-
|
|
619
|
+
]),
|
|
620
|
+
secretId: secretId,
|
|
621
|
+
};
|
|
490
622
|
}));
|
|
491
623
|
}
|
|
492
624
|
/**
|
|
@@ -502,48 +634,53 @@ class IccCryptoXApi {
|
|
|
502
634
|
appendEncryptionKeys(
|
|
503
635
|
//TODO: suggested name: getExtendedEKwithDelegationFromDelegatorToDelegate
|
|
504
636
|
modifiedObject, ownerId, delegateId, secretEncryptionKeyOfObject) {
|
|
505
|
-
this
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
d,
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
.
|
|
531
|
-
|
|
532
|
-
{
|
|
533
|
-
d
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
637
|
+
return __awaiter(this, arguments, void 0, function* () {
|
|
638
|
+
this.throwDetailedExceptionForInvalidParameter('modifiedObject.id', modifiedObject.id, 'appendEncryptionKeys', arguments); //modifiedObject should never be null
|
|
639
|
+
this.throwDetailedExceptionForInvalidParameter('secretEncryptionKeyOfObject', secretEncryptionKeyOfObject, 'appendEncryptionKeys', arguments);
|
|
640
|
+
return this.getDataOwner(ownerId)
|
|
641
|
+
.then(({ dataOwner: owner }) => __awaiter(this, void 0, void 0, function* () {
|
|
642
|
+
const publicKeys = yield this.getPublicKeys();
|
|
643
|
+
const { owner: modifiedOwner, aesExchangeKeys } = yield this.getOrCreateHcPartyKeys(owner, delegateId);
|
|
644
|
+
const importedAESHcPartyKey = yield this.decryptAnyAesExchangeKeyForOwner(aesExchangeKeys, ownerId, ownerId, delegateId, publicKeys);
|
|
645
|
+
return {
|
|
646
|
+
previousDecryptedEncryptionKeys: yield Promise.all(((modifiedObject.encryptionKeys || {})[delegateId] || []).map((d) => (d.key &&
|
|
647
|
+
d.owner === ownerId &&
|
|
648
|
+
this._AES.decrypt(importedAESHcPartyKey.key, (0, binary_utils_1.hex2ua)(d.key), importedAESHcPartyKey.rawKey).catch(() => {
|
|
649
|
+
console.log(`Cannot decrypt encryption key from ${d.owner} to ${d.delegatedTo} for object with id ${modifiedObject.id}:`, modifiedObject);
|
|
650
|
+
return Promise.resolve();
|
|
651
|
+
})) ||
|
|
652
|
+
Promise.resolve())),
|
|
653
|
+
encryptedEncryptionKey: yield this._AES.encrypt(importedAESHcPartyKey.key, (0, binary_utils_1.string2ua)(modifiedObject.id + ':' + secretEncryptionKeyOfObject)),
|
|
654
|
+
};
|
|
655
|
+
}))
|
|
656
|
+
.then(({ previousDecryptedEncryptionKeys, encryptedEncryptionKey }) => {
|
|
657
|
+
//try to limit the extent of the modifications to the delegations by preserving the redundant encryption keys already present and removing duplicates
|
|
658
|
+
//For delegate delegateId, we create:
|
|
659
|
+
// 1. an array of objects { d : {owner,delegatedTo,encrypted(key)}} with one object for the existing encryption keys and the new key concatenated
|
|
660
|
+
// 2. an array of objects { k : decrypted(key)} with one object for the existing delegations and the new key concatenated
|
|
661
|
+
// We merge them to get one array of objects: { d: {owner,delegatedTo,encrypted(key)}, k: decrypted(key)}
|
|
662
|
+
const encryptionKeysCryptedDecrypted = _.merge(((modifiedObject.encryptionKeys || {})[delegateId] || []).map((d) => ({
|
|
663
|
+
d,
|
|
664
|
+
})), (previousDecryptedEncryptionKeys || []).map((dd) => (dd ? (0, binary_utils_1.ua2string)(dd) : null)).map((k) => ({ k })))
|
|
665
|
+
.filter(({ d, k }) => !!k || d.owner !== ownerId) //Only keep the ones created by us that can still be decrypted
|
|
666
|
+
.map(({ d, k }) => ({ d, k: k || this.randomUuid() }))
|
|
667
|
+
.concat([
|
|
668
|
+
{
|
|
669
|
+
d: {
|
|
670
|
+
owner: ownerId,
|
|
671
|
+
delegatedTo: delegateId,
|
|
672
|
+
key: (0, binary_utils_1.ua2hex)(encryptedEncryptionKey),
|
|
673
|
+
},
|
|
674
|
+
k: modifiedObject.id + ':' + secretEncryptionKeyOfObject,
|
|
537
675
|
},
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
};
|
|
676
|
+
]);
|
|
677
|
+
const allEncryptionKeys = _.cloneDeep(modifiedObject.encryptionKeys);
|
|
678
|
+
allEncryptionKeys[delegateId] = _.uniqBy(encryptionKeysCryptedDecrypted, (x) => x.k).map((x) => x.d);
|
|
679
|
+
return {
|
|
680
|
+
encryptionKeys: allEncryptionKeys,
|
|
681
|
+
secretId: secretEncryptionKeyOfObject,
|
|
682
|
+
};
|
|
683
|
+
});
|
|
547
684
|
});
|
|
548
685
|
}
|
|
549
686
|
/**
|
|
@@ -676,15 +813,22 @@ class IccCryptoXApi {
|
|
|
676
813
|
extractKeysHierarchyFromDelegationLikes(
|
|
677
814
|
//TODO suggested name: getSecretIdsOfHcpAndParentsFromGenericDelegations
|
|
678
815
|
hcpartyId, objectId, delegations) {
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
816
|
+
var _a;
|
|
817
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
818
|
+
const { dataOwner: hcp } = yield this.getDataOwner(hcpartyId);
|
|
819
|
+
const extractedKeys = [];
|
|
820
|
+
if ((_a = delegations[hcpartyId]) === null || _a === void 0 ? void 0 : _a.length) {
|
|
821
|
+
const decryptedAndImportedAesHcPartyKeys = yield this.getDecryptedAesExchangeKeysOfDelegateAndParentsFromGenericDelegations(hcpartyId, delegations, false);
|
|
822
|
+
const collatedAesKeysFromDelegatorToHcpartyId = decryptedAndImportedAesHcPartyKeys.reduce((map, k) => (Object.assign(Object.assign({}, map), { [k.delegatorId]: k })), {});
|
|
823
|
+
extractedKeys.push(...(yield this.decryptKeyInDelegationLikes(delegations[hcpartyId], collatedAesKeysFromDelegatorToHcpartyId, objectId)));
|
|
824
|
+
}
|
|
825
|
+
return hcp.parentId
|
|
826
|
+
? [
|
|
827
|
+
...(yield this.extractKeysHierarchyFromDelegationLikes(hcp.parentId, objectId, delegations)),
|
|
828
|
+
{ extractedKeys: extractedKeys, hcpartyId: hcpartyId },
|
|
829
|
+
]
|
|
830
|
+
: [{ extractedKeys: extractedKeys, hcpartyId: hcpartyId }];
|
|
831
|
+
});
|
|
688
832
|
}
|
|
689
833
|
/**
|
|
690
834
|
* Get decrypted generic secret IDs (secretIdSPKs, parentIds, secretIdEKs) from generic delegations (SPKs, CFKs, EKs)
|
|
@@ -703,9 +847,13 @@ class IccCryptoXApi {
|
|
|
703
847
|
//TODO suggested name: getSecretIdsOfHcpAndParentsFromGenericDelegations
|
|
704
848
|
dataOwnerId, objectId, delegations) {
|
|
705
849
|
return this.getDataOwner(dataOwnerId).then(({ dataOwner: hcp }) => (delegations[dataOwnerId] && delegations[dataOwnerId].length
|
|
706
|
-
? this.
|
|
850
|
+
? this.getDecryptedAesExchangeKeysOfDelegateAndParentsFromGenericDelegations(dataOwnerId, delegations, false).then((decryptedAndImportedAesHcPartyKeys) => {
|
|
707
851
|
const collatedAesKeysFromDelegatorToHcpartyId = {};
|
|
708
|
-
decryptedAndImportedAesHcPartyKeys.forEach((k) =>
|
|
852
|
+
decryptedAndImportedAesHcPartyKeys.forEach((k) => {
|
|
853
|
+
var _a;
|
|
854
|
+
;
|
|
855
|
+
((_a = collatedAesKeysFromDelegatorToHcpartyId[k.delegatorId]) !== null && _a !== void 0 ? _a : (collatedAesKeysFromDelegatorToHcpartyId[k.delegatorId] = [])).push(k);
|
|
856
|
+
});
|
|
709
857
|
return this.decryptKeyInDelegationLikes(delegations[dataOwnerId], collatedAesKeysFromDelegatorToHcpartyId, objectId);
|
|
710
858
|
})
|
|
711
859
|
: Promise.resolve([])).then((extractedKeys) => hcp.parentId
|
|
@@ -728,74 +876,90 @@ class IccCryptoXApi {
|
|
|
728
876
|
*/
|
|
729
877
|
decryptKeyInDelegationLikes(
|
|
730
878
|
//TODO: suggested name: getSecretIdsFromGenericDelegations
|
|
731
|
-
delegationsArray,
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
879
|
+
delegationsArray, aesKeysForDataOwnerId, masterId) {
|
|
880
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
881
|
+
const decryptPromises = delegationsArray.map((genericDelegationItem) => __awaiter(this, void 0, void 0, function* () {
|
|
882
|
+
const aesKeys = aesKeysForDataOwnerId[genericDelegationItem.owner];
|
|
883
|
+
if (aesKeys === null || aesKeys === void 0 ? void 0 : aesKeys.length) {
|
|
884
|
+
return aesKeys.reduce((acc, aesKey) => __awaiter(this, void 0, void 0, function* () {
|
|
885
|
+
const accValue = yield acc;
|
|
886
|
+
if (accValue) {
|
|
887
|
+
return accValue;
|
|
888
|
+
}
|
|
889
|
+
try {
|
|
890
|
+
const decryptedGenericDelegationKey = yield this._AES.decrypt(aesKey.key, (0, binary_utils_1.hex2ua)(genericDelegationItem.key), aesKey.rawKey);
|
|
891
|
+
const results = (0, binary_utils_1.ua2string)(decryptedGenericDelegationKey).split(':');
|
|
892
|
+
const objectId = results[0]; //must be the ID of the object, for checksum
|
|
893
|
+
const genericSecretId = results[1];
|
|
894
|
+
const details = 'object ID: ' + masterId + '; generic delegation from ' + genericDelegationItem.owner + ' to ' + genericDelegationItem.delegatedTo;
|
|
895
|
+
if (!objectId)
|
|
896
|
+
console.warn('Object id is empty; ' + details);
|
|
897
|
+
if (!genericSecretId)
|
|
898
|
+
console.warn('Secret id is empty; ' + details);
|
|
899
|
+
if (objectId !== masterId) {
|
|
900
|
+
/*console.log(
|
|
901
|
+
"Cryptographic mistake: object ID is not equal to the expected concatenated id within decrypted generic delegation. This may happen when patients have been merged; " +
|
|
902
|
+
details
|
|
903
|
+
)*/
|
|
904
|
+
}
|
|
905
|
+
return genericSecretId;
|
|
906
|
+
}
|
|
907
|
+
catch (err) {
|
|
908
|
+
console.log(`Could not decrypt generic delegation in object with ID: ${masterId} from ${genericDelegationItem.owner} to ${genericDelegationItem.delegatedTo}: ${err}`);
|
|
909
|
+
console.log(`AES key is: ${aesKey.rawKey}. Encrypted data is ${genericDelegationItem.key}.`);
|
|
910
|
+
return undefined;
|
|
911
|
+
}
|
|
912
|
+
}), Promise.resolve());
|
|
913
|
+
}
|
|
914
|
+
else {
|
|
915
|
+
console.log(`Could not find aes key for object with ID: ${masterId}`);
|
|
916
|
+
}
|
|
917
|
+
}));
|
|
918
|
+
return Promise.all(decryptPromises).then((genericSecretId) => genericSecretId.filter((id) => !!id));
|
|
919
|
+
});
|
|
920
|
+
}
|
|
921
|
+
getPublicKeyFromPrivateKey(privateKey, dataOwner) {
|
|
922
|
+
var _a;
|
|
923
|
+
if (!privateKey.n || !privateKey.e) {
|
|
924
|
+
throw new Error('No public key can be deduced from incomplete private key');
|
|
925
|
+
}
|
|
926
|
+
const publicKeyFromPrivateKey = (0, utils_1.jwk2spki)(privateKey);
|
|
927
|
+
const publicKeys = [dataOwner.publicKey].concat(Object.keys((_a = dataOwner.aesExchangeKeys) !== null && _a !== void 0 ? _a : {})).filter((x) => !!x);
|
|
928
|
+
if (!publicKeys.length) {
|
|
929
|
+
throw new Error('No public key has been defined for hcp');
|
|
764
930
|
}
|
|
765
|
-
|
|
931
|
+
const publicKey = publicKeys.find((it) => it === publicKeyFromPrivateKey);
|
|
932
|
+
if (!publicKey) {
|
|
933
|
+
throw new Error('No public key can be found for this private key');
|
|
934
|
+
}
|
|
935
|
+
return publicKey;
|
|
766
936
|
}
|
|
767
937
|
loadKeyPairsAsTextInBrowserLocalStorage(healthcarePartyId, privateKey) {
|
|
768
|
-
return this
|
|
769
|
-
|
|
770
|
-
.
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
.then((keyPair) => {
|
|
777
|
-
this._RSA.rsaKeyPairs[healthcarePartyId] = keyPair;
|
|
778
|
-
return this._RSA.exportKeys(keyPair, 'jwk', 'jwk');
|
|
779
|
-
})
|
|
780
|
-
.then((exportedKeyPair) => {
|
|
781
|
-
return this._RSA.storeKeyPair(healthcarePartyId, exportedKeyPair);
|
|
938
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
939
|
+
const { dataOwner } = yield this.getDataOwner(healthcarePartyId);
|
|
940
|
+
const privateKeyInJwk = (0, utils_1.pkcs8ToJwk)(privateKey);
|
|
941
|
+
const publicKey = this.getPublicKeyFromPrivateKey(privateKeyInJwk, dataOwner);
|
|
942
|
+
const keyPair = yield this._RSA.importKeyPair('jwk', privateKeyInJwk, 'jwk', (0, utils_1.spkiToJwk)((0, binary_utils_1.hex2ua)(publicKey)));
|
|
943
|
+
this.rsaKeyPairs[healthcarePartyId] = keyPair;
|
|
944
|
+
const exportedKeyPair = yield this._RSA.exportKeys(keyPair, 'jwk', 'jwk');
|
|
945
|
+
return this.storeKeyPair(`${healthcarePartyId}.${publicKey.slice(-32)}`, exportedKeyPair);
|
|
782
946
|
});
|
|
783
947
|
}
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
this.
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
.
|
|
798
|
-
return this.
|
|
948
|
+
loadKeyPairsAsJwkInBrowserLocalStorage(healthcarePartyId, privateKey) {
|
|
949
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
950
|
+
const { dataOwner } = yield this.getDataOwner(healthcarePartyId);
|
|
951
|
+
if ((!privateKey.n || !privateKey.e) && dataOwner.publicKey) {
|
|
952
|
+
//Fallback on default publicKey
|
|
953
|
+
console.warn('An incomplete key has been completed using the default public key of the data owner');
|
|
954
|
+
const publicKeyInJwk = (0, utils_1.spkiToJwk)((0, binary_utils_1.hex2ua)(dataOwner.publicKey));
|
|
955
|
+
privateKey.n = publicKeyInJwk.n;
|
|
956
|
+
privateKey.e = publicKeyInJwk.e;
|
|
957
|
+
}
|
|
958
|
+
const publicKey = this.getPublicKeyFromPrivateKey(privateKey, dataOwner);
|
|
959
|
+
const keyPair = yield this._RSA.importKeyPair('jwk', privateKey, 'jwk', (0, utils_1.spkiToJwk)((0, binary_utils_1.hex2ua)(publicKey)));
|
|
960
|
+
this.rsaKeyPairs[healthcarePartyId] = keyPair;
|
|
961
|
+
const exportedKeyPair = yield this._RSA.exportKeys(keyPair, 'jwk', 'jwk');
|
|
962
|
+
return this.storeKeyPair(`${healthcarePartyId}.${publicKey.slice(-32)}`, exportedKeyPair);
|
|
799
963
|
});
|
|
800
964
|
}
|
|
801
965
|
// noinspection JSUnusedGlobalSymbols
|
|
@@ -807,14 +971,16 @@ class IccCryptoXApi {
|
|
|
807
971
|
fr.onload = (e) => {
|
|
808
972
|
//TODO remove any
|
|
809
973
|
const privateKey = e.target.result;
|
|
810
|
-
this.loadKeyPairsAsTextInBrowserLocalStorage(healthcarePartyId, binary_utils_1.hex2ua(privateKey))
|
|
974
|
+
this.loadKeyPairsAsTextInBrowserLocalStorage(healthcarePartyId, (0, binary_utils_1.hex2ua)(privateKey))
|
|
975
|
+
.then(() => resolve())
|
|
976
|
+
.catch(reject);
|
|
811
977
|
};
|
|
812
978
|
fr.readAsText(file);
|
|
813
979
|
});
|
|
814
980
|
}
|
|
815
981
|
// noinspection JSUnusedGlobalSymbols
|
|
816
982
|
saveKeychainInBrowserLocalStorage(id, keychain) {
|
|
817
|
-
localStorage.setItem(this.keychainLocalStoreIdPrefix + id, binary_utils_1.b2a(new Uint8Array(keychain).reduce((data, byte) => data + String.fromCharCode(byte), '')));
|
|
983
|
+
localStorage.setItem(this.keychainLocalStoreIdPrefix + id, (0, binary_utils_1.b2a)(new Uint8Array(keychain).reduce((data, byte) => data + String.fromCharCode(byte), '')));
|
|
818
984
|
}
|
|
819
985
|
saveKeychainInBrowserLocalStorageAsBase64(id, keyChainB64) {
|
|
820
986
|
localStorage.setItem(this.keychainLocalStoreIdPrefix + id, keyChainB64);
|
|
@@ -854,13 +1020,13 @@ class IccCryptoXApi {
|
|
|
854
1020
|
if (!!aesKey && !!crt) {
|
|
855
1021
|
let crtEncrypted = null;
|
|
856
1022
|
try {
|
|
857
|
-
crtEncrypted = yield this.AES.encrypt(aesKey, new Uint8Array(binary_utils_1.string2ua(atob(crt))));
|
|
1023
|
+
crtEncrypted = yield this.AES.encrypt(aesKey, new Uint8Array((0, binary_utils_1.string2ua)(atob(crt))));
|
|
858
1024
|
}
|
|
859
1025
|
catch (e) {
|
|
860
1026
|
console.error('Error while encrypting the certificate', e);
|
|
861
1027
|
}
|
|
862
1028
|
// add the encrypted certificate to the options
|
|
863
|
-
_.set(opts, this.hcpPreferenceKeyEhealthCert, binary_utils_1.ua2string(new Uint8Array(crtEncrypted)));
|
|
1029
|
+
_.set(opts, this.hcpPreferenceKeyEhealthCert, (0, binary_utils_1.ua2string)(new Uint8Array(crtEncrypted)));
|
|
864
1030
|
}
|
|
865
1031
|
const crtValidityDate = this.getKeychainValidityDateInBrowserLocalStorage(hcp.id);
|
|
866
1032
|
if (!!crtValidityDate) {
|
|
@@ -875,7 +1041,7 @@ class IccCryptoXApi {
|
|
|
875
1041
|
return this.hcpartyBaseApi.getHealthcareParty(hcpId).then((hcp) => __awaiter(this, void 0, void 0, function* () {
|
|
876
1042
|
let crtCryp = null;
|
|
877
1043
|
if (!!hcp.options && !!hcp.options[this.hcpPreferenceKeyEhealthCert]) {
|
|
878
|
-
crtCryp = binary_utils_1.string2ua(hcp.options[this.hcpPreferenceKeyEhealthCert]);
|
|
1044
|
+
crtCryp = (0, binary_utils_1.string2ua)(hcp.options[this.hcpPreferenceKeyEhealthCert]);
|
|
879
1045
|
}
|
|
880
1046
|
const crtValidityDate = _.get(hcp.options, this.hcpPreferenceKeyEhealthCertDate);
|
|
881
1047
|
// store the validity date
|
|
@@ -941,50 +1107,310 @@ class IccCryptoXApi {
|
|
|
941
1107
|
// noinspection JSUnusedGlobalSymbols
|
|
942
1108
|
loadKeychainFromBrowserLocalStorage(id) {
|
|
943
1109
|
const lsItem = localStorage.getItem('org.taktik.icure.ehealth.keychain.' + id);
|
|
944
|
-
return lsItem !== null ? binary_utils_1.b64_2uas(lsItem) : null;
|
|
1110
|
+
return lsItem !== null ? (0, binary_utils_1.b64_2uas)(lsItem) : null;
|
|
1111
|
+
}
|
|
1112
|
+
/**
|
|
1113
|
+
*
|
|
1114
|
+
* @param id
|
|
1115
|
+
* @param keyPair should be JWK
|
|
1116
|
+
*/
|
|
1117
|
+
storeKeyPair(id, keyPair) {
|
|
1118
|
+
if (typeof Storage === 'undefined') {
|
|
1119
|
+
console.log('Your browser does not support HTML5 Browser Local Storage !');
|
|
1120
|
+
throw 'Your browser does not support HTML5 Browser Local Storage !';
|
|
1121
|
+
}
|
|
1122
|
+
//TODO encryption
|
|
1123
|
+
localStorage.setItem(this.rsaLocalStoreIdPrefix + id, JSON.stringify(keyPair));
|
|
1124
|
+
}
|
|
1125
|
+
/**
|
|
1126
|
+
* loads the RSA key pair (hcparty) in JWK from local storage, not imported
|
|
1127
|
+
*
|
|
1128
|
+
* @param id doc id - hcpartyId
|
|
1129
|
+
* @param publicKeyFingerPrint the 32 last characters of public key this private key is associated with
|
|
1130
|
+
* @returns {Object} it is in JWK - not imported
|
|
1131
|
+
*/
|
|
1132
|
+
loadKeyPairNotImported(id, publicKeyFingerPrint) {
|
|
1133
|
+
var _a;
|
|
1134
|
+
if (typeof Storage === 'undefined') {
|
|
1135
|
+
console.log('Your browser does not support HTML5 Browser Local Storage !');
|
|
1136
|
+
throw 'Your browser does not support HTML5 Browser Local Storage !';
|
|
1137
|
+
}
|
|
1138
|
+
//TODO decryption
|
|
1139
|
+
const item = (_a = (publicKeyFingerPrint && localStorage.getItem(this.rsaLocalStoreIdPrefix + id + '.' + publicKeyFingerPrint))) !== null && _a !== void 0 ? _a : localStorage.getItem(this.rsaLocalStoreIdPrefix + id);
|
|
1140
|
+
if (!item) {
|
|
1141
|
+
console.warn(`No key can be found in local storage for id ${id} and publicKeyFingerPrint ${publicKeyFingerPrint}`);
|
|
1142
|
+
}
|
|
1143
|
+
return JSON.parse(item);
|
|
1144
|
+
}
|
|
1145
|
+
/**
|
|
1146
|
+
* Loads and imports the RSA key pair (hcparty) from local storage
|
|
1147
|
+
*
|
|
1148
|
+
* @param id doc id - hcPartyId
|
|
1149
|
+
* @returns {Promise} -> {CryptoKey} - imported RSA
|
|
1150
|
+
*/
|
|
1151
|
+
loadKeyPairImported(id) {
|
|
1152
|
+
return new Promise((resolve, reject) => {
|
|
1153
|
+
try {
|
|
1154
|
+
const jwkKey = localStorage.getItem(this.rsaLocalStoreIdPrefix + id);
|
|
1155
|
+
if (jwkKey) {
|
|
1156
|
+
const jwkKeyPair = JSON.parse(jwkKey);
|
|
1157
|
+
if (jwkKeyPair.publicKey && jwkKeyPair.privateKey) {
|
|
1158
|
+
this._RSA.importKeyPair('jwk', jwkKeyPair.privateKey, 'jwk', jwkKeyPair.publicKey).then(resolve, (err) => {
|
|
1159
|
+
console.log('Error in RSA.importKeyPair: ' + err);
|
|
1160
|
+
reject(err);
|
|
1161
|
+
});
|
|
1162
|
+
}
|
|
1163
|
+
else {
|
|
1164
|
+
const message = 'Error in RSA.importKeyPair: Invalid key';
|
|
1165
|
+
console.log(message);
|
|
1166
|
+
reject(Error(message));
|
|
1167
|
+
}
|
|
1168
|
+
}
|
|
1169
|
+
else {
|
|
1170
|
+
const message = 'Error in RSA.importKeyPair: Missing key';
|
|
1171
|
+
console.log(message);
|
|
1172
|
+
reject(Error(message));
|
|
1173
|
+
}
|
|
1174
|
+
}
|
|
1175
|
+
catch (err) {
|
|
1176
|
+
reject(err);
|
|
1177
|
+
}
|
|
1178
|
+
});
|
|
1179
|
+
}
|
|
1180
|
+
addNewKeyPairForOwnerId(maintenanceTasksApi, user, ownerId, generateTransferKey = true) {
|
|
1181
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
1182
|
+
return this.addNewKeyPairForOwner(maintenanceTasksApi, user, yield this.getDataOwner(ownerId), generateTransferKey);
|
|
1183
|
+
});
|
|
1184
|
+
}
|
|
1185
|
+
addNewKeyPairForOwner(maintenanceTasksApi, user, cdo, generateTransferKey = true) {
|
|
1186
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
1187
|
+
const { publicKey, privateKey } = yield this.RSA.generateKeyPair();
|
|
1188
|
+
const publicKeyHex = (0, binary_utils_1.ua2hex)(yield this.RSA.exportKey(publicKey, 'spki'));
|
|
1189
|
+
const gen = (yield this._AES.generateCryptoKey(true));
|
|
1190
|
+
yield this.cacheKeyPair({ publicKey: yield this.RSA.exportKey(publicKey, 'jwk'), privateKey: yield this.RSA.exportKey(privateKey, 'jwk') });
|
|
1191
|
+
const { type: ownerType, dataOwner: ownerToUpdate } = yield this.createOrUpdateAesExchangeKeysFor(cdo, gen, {
|
|
1192
|
+
pubKey: publicKey,
|
|
1193
|
+
privKey: privateKey,
|
|
1194
|
+
}).then((dataOwnerWithUpdatedAesKeys) => generateTransferKey
|
|
1195
|
+
? this.createOrUpdateTransferKeysFor(dataOwnerWithUpdatedAesKeys, gen, { pubKey: publicKey, privKey: privateKey })
|
|
1196
|
+
: dataOwnerWithUpdatedAesKeys);
|
|
1197
|
+
const modifiedDataOwnerAndType = ownerType === 'hcp'
|
|
1198
|
+
? yield (this.dataOwnerCache[ownerToUpdate.id] = this.hcpartyBaseApi
|
|
1199
|
+
.modifyHealthcareParty(ownerToUpdate)
|
|
1200
|
+
.then((x) => ({ type: 'hcp', dataOwner: x })))
|
|
1201
|
+
: ownerType === 'patient'
|
|
1202
|
+
? yield (this.dataOwnerCache[ownerToUpdate.id] = this.patientBaseApi
|
|
1203
|
+
.modifyPatient(ownerToUpdate)
|
|
1204
|
+
.then((x) => ({ type: 'patient', dataOwner: x })))
|
|
1205
|
+
: yield (this.dataOwnerCache[ownerToUpdate.id] = this.deviceBaseApi
|
|
1206
|
+
.updateDevice(ownerToUpdate)
|
|
1207
|
+
.then((x) => ({ type: 'device', dataOwner: x })));
|
|
1208
|
+
const sentMaintenanceTasks = yield this.sendMaintenanceTasks(maintenanceTasksApi, user, modifiedDataOwnerAndType.dataOwner, publicKey);
|
|
1209
|
+
return {
|
|
1210
|
+
dataOwner: sentMaintenanceTasks.length
|
|
1211
|
+
? yield this.retrieveDataOwnerInfoAfterPotentialUpdate(modifiedDataOwnerAndType.dataOwner)
|
|
1212
|
+
: modifiedDataOwnerAndType.dataOwner,
|
|
1213
|
+
publicKey: publicKeyHex,
|
|
1214
|
+
privateKey: (0, binary_utils_1.ua2hex)((yield this.RSA.exportKey(privateKey, 'pkcs8'))),
|
|
1215
|
+
};
|
|
1216
|
+
});
|
|
1217
|
+
}
|
|
1218
|
+
createOrUpdateAesExchangeKeysFor(cdo, decryptedAesExchangeKey, keyToEncrypt) {
|
|
1219
|
+
var _a;
|
|
1220
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
1221
|
+
const publicKeyHex = (0, binary_utils_1.ua2hex)(yield this.RSA.exportKey(keyToEncrypt.pubKey, 'spki'));
|
|
1222
|
+
const existingAesExchangeKeys = (_a = cdo.dataOwner.aesExchangeKeys) !== null && _a !== void 0 ? _a : {};
|
|
1223
|
+
existingAesExchangeKeys[publicKeyHex] = yield this.encryptAesKeyFor(decryptedAesExchangeKey, cdo.dataOwner, keyToEncrypt.pubKey);
|
|
1224
|
+
return { type: cdo.type, dataOwner: Object.assign(Object.assign({}, cdo.dataOwner), { aesExchangeKeys: existingAesExchangeKeys }) };
|
|
1225
|
+
});
|
|
1226
|
+
}
|
|
1227
|
+
createOrUpdateTransferKeysFor(dataOwner, decryptedAesExchangeKey, keyToEncrypt) {
|
|
1228
|
+
var _a;
|
|
1229
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
1230
|
+
const pubKeyToEncryptHex = (0, binary_utils_1.ua2hex)(yield this.RSA.exportKey(keyToEncrypt.pubKey, 'spki'));
|
|
1231
|
+
const encryptedKey = (0, binary_utils_1.ua2hex)(yield this._AES.encrypt(yield this._AES.importKey('raw', (0, binary_utils_1.hex2ua)(decryptedAesExchangeKey)), (yield this.RSA.exportKey(keyToEncrypt.privKey, 'pkcs8')), decryptedAesExchangeKey));
|
|
1232
|
+
const dataOwnerExistingPubKeys = Array.from(yield this.getDataOwnerHexPublicKeys(dataOwner.dataOwner));
|
|
1233
|
+
const transferKeys = (0, utils_1.fold)(dataOwnerExistingPubKeys, (_a = dataOwner.dataOwner.transferKeys) !== null && _a !== void 0 ? _a : {}, (pubAcc, pubKeyHex) => {
|
|
1234
|
+
var _a;
|
|
1235
|
+
if (pubKeyHex !== pubKeyToEncryptHex) {
|
|
1236
|
+
const existingKeys = (_a = pubAcc[pubKeyHex]) !== null && _a !== void 0 ? _a : {};
|
|
1237
|
+
existingKeys[pubKeyToEncryptHex] = encryptedKey;
|
|
1238
|
+
pubAcc[pubKeyHex] = existingKeys;
|
|
1239
|
+
}
|
|
1240
|
+
return pubAcc;
|
|
1241
|
+
});
|
|
1242
|
+
return { type: dataOwner.type, dataOwner: Object.assign(Object.assign({}, dataOwner.dataOwner), { transferKeys: transferKeys }) };
|
|
1243
|
+
});
|
|
1244
|
+
}
|
|
1245
|
+
encryptAesKeyFor(aesKey, dataOwner, doNewPublicKey) {
|
|
1246
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
1247
|
+
const dataOwnerAllPubKeys = [doNewPublicKey].concat(yield this.getDataOwnerPublicKeys(dataOwner));
|
|
1248
|
+
const encrAes = {};
|
|
1249
|
+
for (const pubKey of dataOwnerAllPubKeys) {
|
|
1250
|
+
encrAes[(0, binary_utils_1.ua2hex)(yield this.RSA.exportKey(pubKey, 'spki')).slice(-32)] = (0, binary_utils_1.ua2hex)(yield this._RSA.encrypt(pubKey, (0, binary_utils_1.hex2ua)(aesKey)));
|
|
1251
|
+
}
|
|
1252
|
+
return { [dataOwner.id]: encrAes };
|
|
1253
|
+
});
|
|
1254
|
+
}
|
|
1255
|
+
retrieveDataOwnerInfoAfterPotentialUpdate(dataOwnerToUpdate) {
|
|
1256
|
+
return this.getDataOwner(dataOwnerToUpdate.id).then(({ type, dataOwner }) => {
|
|
1257
|
+
var _a, _b, _c, _d;
|
|
1258
|
+
return {
|
|
1259
|
+
type: type,
|
|
1260
|
+
dataOwner: Object.assign(Object.assign({}, dataOwner), { transferKeys: dataOwnerToUpdate.transferKeys, hcPartyKeys: (0, utils_1.fold)(Object.entries((_a = dataOwnerToUpdate.hcPartyKeys) !== null && _a !== void 0 ? _a : {}), (_b = dataOwner.hcPartyKeys) !== null && _b !== void 0 ? _b : {}, (acc, [delegateId, hcKeys]) => {
|
|
1261
|
+
acc[delegateId] = hcKeys;
|
|
1262
|
+
return acc;
|
|
1263
|
+
}), aesExchangeKeys: (0, utils_1.fold)(Object.entries((_c = dataOwnerToUpdate.aesExchangeKeys) !== null && _c !== void 0 ? _c : {}), (_d = dataOwner.aesExchangeKeys) !== null && _d !== void 0 ? _d : {}, (pubAcc, [pubKey, newAesExcKeys]) => {
|
|
1264
|
+
var _a;
|
|
1265
|
+
const existingKeys = (_a = pubAcc[pubKey]) !== null && _a !== void 0 ? _a : {};
|
|
1266
|
+
pubAcc[pubKey] = (0, utils_1.fold)(Object.entries(newAesExcKeys), existingKeys, (delAcc, [delId, delKeys]) => {
|
|
1267
|
+
delAcc[delId] = delKeys;
|
|
1268
|
+
return delAcc;
|
|
1269
|
+
});
|
|
1270
|
+
return pubAcc;
|
|
1271
|
+
}) }),
|
|
1272
|
+
};
|
|
1273
|
+
});
|
|
1274
|
+
}
|
|
1275
|
+
sendMaintenanceTasks(maintenanceTaskApi, user, dataOwner, newPublicKey) {
|
|
1276
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
1277
|
+
const hexNewPubKey = (0, binary_utils_1.ua2hex)(yield this.RSA.exportKey(newPublicKey, 'spki'));
|
|
1278
|
+
const nonAccessiblePubKeys = Array.from(this.getDataOwnerHexPublicKeys(dataOwner).values())
|
|
1279
|
+
.filter((existingPubKey) => existingPubKey != hexNewPubKey)
|
|
1280
|
+
.filter((existingPubKey) => !Object.values(this.rsaKeyPairs).find(({ publicKey, privateKey }) => __awaiter(this, void 0, void 0, function* () { return (0, binary_utils_1.ua2hex)(yield this.RSA.exportKey(publicKey, 'spki')) == existingPubKey; })));
|
|
1281
|
+
if (nonAccessiblePubKeys.length) {
|
|
1282
|
+
const tasksForDelegates = Object.entries(yield this.getEncryptedAesExchangeKeysForDelegate(dataOwner.id))
|
|
1283
|
+
.filter(([delegatorId]) => delegatorId != dataOwner.id)
|
|
1284
|
+
.flatMap(([delegatorId, delegatorKeys]) => {
|
|
1285
|
+
return Object.entries(delegatorKeys).flatMap(([, aesExchangeKeys]) => {
|
|
1286
|
+
return Object.keys(aesExchangeKeys).map((delegatePubKey) => {
|
|
1287
|
+
return { delegateId: delegatorId, maintenanceTask: this.createMaintenanceTask(dataOwner, delegatePubKey) };
|
|
1288
|
+
});
|
|
1289
|
+
});
|
|
1290
|
+
});
|
|
1291
|
+
const tasksForDelegator = Object.entries(yield this.getEncryptedAesExchangeKeys(dataOwner, dataOwner.id)).flatMap(([doPubKey, delegateKeys]) => {
|
|
1292
|
+
return Object.keys(delegateKeys)
|
|
1293
|
+
.filter((delegateId) => delegateId != dataOwner.id)
|
|
1294
|
+
.map((delegateId) => {
|
|
1295
|
+
return { delegateId: delegateId, maintenanceTask: this.createMaintenanceTask(dataOwner, doPubKey) };
|
|
1296
|
+
});
|
|
1297
|
+
});
|
|
1298
|
+
return Promise.all(tasksForDelegates
|
|
1299
|
+
.concat(tasksForDelegator)
|
|
1300
|
+
.map(({ delegateId, maintenanceTask }) => __awaiter(this, void 0, void 0, function* () {
|
|
1301
|
+
const taskToCreate = yield (maintenanceTaskApi === null || maintenanceTaskApi === void 0 ? void 0 : maintenanceTaskApi.newInstance(user, maintenanceTask, delegateId));
|
|
1302
|
+
return taskToCreate ? maintenanceTaskApi === null || maintenanceTaskApi === void 0 ? void 0 : maintenanceTaskApi.createMaintenanceTaskWithUser(user, taskToCreate) : undefined;
|
|
1303
|
+
}))
|
|
1304
|
+
.filter((createdTask) => createdTask != undefined));
|
|
1305
|
+
}
|
|
1306
|
+
else {
|
|
1307
|
+
return [];
|
|
1308
|
+
}
|
|
1309
|
+
});
|
|
1310
|
+
}
|
|
1311
|
+
createMaintenanceTask(concernedDataOwner, concernedPubKey) {
|
|
1312
|
+
return new models_1.MaintenanceTask({
|
|
1313
|
+
id: this.randomUuid(),
|
|
1314
|
+
taskType: 'updateAesExchangeKey',
|
|
1315
|
+
status: models_1.MaintenanceTask.StatusEnum.Pending,
|
|
1316
|
+
properties: [
|
|
1317
|
+
new models_1.PropertyStub({
|
|
1318
|
+
id: 'dataOwnerConcernedId',
|
|
1319
|
+
type: new models_1.PropertyTypeStub({ type: models_1.PropertyTypeStub.TypeEnum.STRING }),
|
|
1320
|
+
typedValue: new models_1.TypedValueObject({
|
|
1321
|
+
type: models_1.TypedValueObject.TypeEnum.STRING,
|
|
1322
|
+
stringValue: concernedDataOwner.id,
|
|
1323
|
+
}),
|
|
1324
|
+
}),
|
|
1325
|
+
new models_1.PropertyStub({
|
|
1326
|
+
id: 'dataOwnerConcernedPubKey',
|
|
1327
|
+
type: new models_1.PropertyTypeStub({ type: models_1.PropertyTypeStub.TypeEnum.STRING }),
|
|
1328
|
+
typedValue: new models_1.TypedValueObject({
|
|
1329
|
+
type: models_1.TypedValueObject.TypeEnum.STRING,
|
|
1330
|
+
stringValue: concernedPubKey,
|
|
1331
|
+
}),
|
|
1332
|
+
}),
|
|
1333
|
+
],
|
|
1334
|
+
});
|
|
1335
|
+
}
|
|
1336
|
+
getDataOwnerHexPublicKeys(dataOwner) {
|
|
1337
|
+
return new Set((dataOwner.publicKey ? [dataOwner.publicKey] : [])
|
|
1338
|
+
.concat(dataOwner.aesExchangeKeys ? Object.keys(dataOwner.aesExchangeKeys) : [])
|
|
1339
|
+
.filter((pubKey) => !!pubKey));
|
|
1340
|
+
}
|
|
1341
|
+
getDataOwnerPublicKeys(dataOwner) {
|
|
1342
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
1343
|
+
return yield Promise.all(Array.from(this.getDataOwnerHexPublicKeys(dataOwner)).map((pubKey) => __awaiter(this, void 0, void 0, function* () { return yield this._RSA.importKey('jwk', (0, utils_1.spkiToJwk)((0, binary_utils_1.hex2ua)(pubKey)), ['encrypt']); })));
|
|
1344
|
+
});
|
|
945
1345
|
}
|
|
946
1346
|
generateKeyForDelegate(ownerId, delegateId) {
|
|
947
1347
|
//Preload hcp and patient because we need them and they are going to be invalidated from the caches
|
|
948
|
-
return
|
|
949
|
-
|
|
1348
|
+
return (0, utils_1.notConcurrent)(this.generateKeyConcurrencyMap, ownerId, () => __awaiter(this, void 0, void 0, function* () {
|
|
1349
|
+
var _a, _b, _c, _d, _e;
|
|
1350
|
+
const [{ type: ownerType, dataOwner: owner }, { dataOwner: delegate }] = yield Promise.all([
|
|
1351
|
+
this.getDataOwner(ownerId),
|
|
1352
|
+
this.getDataOwner(delegateId),
|
|
1353
|
+
]);
|
|
1354
|
+
const ownerLegacyPublicKey = owner.publicKey;
|
|
1355
|
+
const availablePublicKeysFingerprints = Object.keys(this.rsaKeyPairs);
|
|
1356
|
+
const availableOwnerPublicKeys = [
|
|
1357
|
+
ownerLegacyPublicKey,
|
|
1358
|
+
...Object.keys(owner.aesExchangeKeys || {}).filter((x) => x !== ownerLegacyPublicKey),
|
|
1359
|
+
].filter((k) => !!k && availablePublicKeysFingerprints.some((fp) => k.endsWith(fp)));
|
|
1360
|
+
const selectedPublicKey = availableOwnerPublicKeys[0];
|
|
1361
|
+
if (!selectedPublicKey) {
|
|
1362
|
+
throw new Error(`Invalid owner, no public key, keypairs have not be set for ${ownerId}`);
|
|
1363
|
+
}
|
|
1364
|
+
if ((owner.hcPartyKeys || {})[delegateId] ||
|
|
1365
|
+
Object.values(owner.aesExchangeKeys || {}).some((x) => x[delegateId] && Object.keys(x[delegateId]).some((k) => availablePublicKeysFingerprints.includes(k)))) {
|
|
950
1366
|
return owner;
|
|
951
1367
|
}
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
1368
|
+
let ownerCombinedAesExchangeKeys = (_a = owner.aesExchangeKeys) !== null && _a !== void 0 ? _a : {};
|
|
1369
|
+
if (ownerLegacyPublicKey && !((_b = owner.aesExchangeKeys) !== null && _b !== void 0 ? _b : {})[ownerLegacyPublicKey]) {
|
|
1370
|
+
//Transfer keys from old structure (hcparty keys) to new one (aesExchangeKeys)
|
|
1371
|
+
const unknownDataOwnerCounterPartIds = Object.keys((_c = owner.hcPartyKeys) !== null && _c !== void 0 ? _c : {}).filter((x) => x !== ownerId && x !== delegateId);
|
|
1372
|
+
const counterParts = [
|
|
1373
|
+
owner,
|
|
1374
|
+
delegate,
|
|
1375
|
+
...(yield Promise.all(unknownDataOwnerCounterPartIds.map((cpid) => this.getDataOwner(cpid).then((dot) => dot.dataOwner)))),
|
|
1376
|
+
];
|
|
1377
|
+
ownerCombinedAesExchangeKeys = Object.assign({ [ownerLegacyPublicKey]: Object.entries((_d = owner.hcPartyKeys) !== null && _d !== void 0 ? _d : {}).reduce((map, [hcpId, keys]) => {
|
|
1378
|
+
var _a, _b;
|
|
1379
|
+
return (Object.assign(Object.assign(Object.assign({}, map), { [hcpId]: { [ownerLegacyPublicKey]: keys[0], [(_b = (_a = counterParts.find((x) => x.id === hcpId)) === null || _a === void 0 ? void 0 : _a.publicKey) !== null && _b !== void 0 ? _b : '']: keys[1] } }), {}));
|
|
1380
|
+
}, {}) }, ownerCombinedAesExchangeKeys);
|
|
1381
|
+
}
|
|
1382
|
+
const delegatePublicKeys = [delegate.publicKey, ...Object.keys((_e = delegate.aesExchangeKeys) !== null && _e !== void 0 ? _e : {}).filter((x) => x !== delegate.publicKey)].filter((x) => !!x);
|
|
1383
|
+
if (!delegatePublicKeys.length) {
|
|
1384
|
+
throw new Error(`Invalid delegate, no public key, keypairs have not be set for ${delegateId}`);
|
|
1385
|
+
}
|
|
1386
|
+
const genProm = this._AES.generateCryptoKey(true).then((AESKey) => __awaiter(this, void 0, void 0, function* () {
|
|
1387
|
+
var _f, _g;
|
|
1388
|
+
const allPubKeys = [...availableOwnerPublicKeys, ...delegatePublicKeys];
|
|
1389
|
+
const encryptedAesKeys = yield allPubKeys.reduce((map, pubK) => __awaiter(this, void 0, void 0, function* () {
|
|
1390
|
+
return (Object.assign(Object.assign({}, (yield map)), { [pubK.slice(-32)]: (0, binary_utils_1.ua2hex)(yield this._RSA.encrypt(yield this._RSA.importKey('jwk', (0, utils_1.spkiToJwk)((0, binary_utils_1.hex2ua)(pubK)), ['encrypt']), (0, binary_utils_1.hex2ua)(AESKey))) }));
|
|
1391
|
+
}), Promise.resolve({}));
|
|
1392
|
+
if (delegate.publicKey && ownerLegacyPublicKey) {
|
|
1393
|
+
owner.hcPartyKeys[delegateId] = [encryptedAesKeys[ownerLegacyPublicKey.slice(-32)], encryptedAesKeys[delegate.publicKey.slice(-32)]];
|
|
1394
|
+
}
|
|
1395
|
+
owner.aesExchangeKeys = Object.assign(Object.assign({}, (ownerCombinedAesExchangeKeys !== null && ownerCombinedAesExchangeKeys !== void 0 ? ownerCombinedAesExchangeKeys : {})), { [selectedPublicKey]: Object.assign(Object.assign({}, ((_g = (_f = owner.aesExchangeKeys) === null || _f === void 0 ? void 0 : _f[selectedPublicKey]) !== null && _g !== void 0 ? _g : {})), { [delegateId]: encryptedAesKeys }) });
|
|
1396
|
+
return new Promise((resolve, reject) => {
|
|
1397
|
+
ownerType === 'hcp'
|
|
1398
|
+
? (this.dataOwnerCache[owner.id] = this.hcpartyBaseApi
|
|
1399
|
+
.modifyHealthcareParty(owner)
|
|
1400
|
+
.then((x) => ({ type: 'hcp', dataOwner: x })))
|
|
1401
|
+
.then((x) => resolve(['hcp', x.dataOwner]))
|
|
1402
|
+
.catch((e) => reject(e))
|
|
1403
|
+
: ownerType === 'patient'
|
|
1404
|
+
? (this.dataOwnerCache[owner.id] = this.patientBaseApi.modifyPatient(owner).then((x) => ({ type: 'patient', dataOwner: x })))
|
|
1405
|
+
.then((x) => resolve(['patient', x.dataOwner]))
|
|
1406
|
+
.catch((e) => reject(e))
|
|
1407
|
+
: (this.dataOwnerCache[owner.id] = this.deviceBaseApi.updateDevice(owner).then((x) => ({ type: 'device', dataOwner: x })))
|
|
1408
|
+
.then((x) => resolve(['device', x.dataOwner]))
|
|
1409
|
+
.catch((e) => reject(e));
|
|
1410
|
+
});
|
|
1411
|
+
}));
|
|
986
1412
|
this.hcPartyKeysRequestsCache[delegateId] = genProm.then(() => {
|
|
987
|
-
return this.
|
|
1413
|
+
return this.forceGetEncryptedAesExchangeKeysForDelegate(delegateId);
|
|
988
1414
|
});
|
|
989
1415
|
return genProm.then((res) => {
|
|
990
1416
|
return res[1];
|
|
@@ -1005,22 +1431,28 @@ class IccCryptoXApi {
|
|
|
1005
1431
|
}
|
|
1006
1432
|
// noinspection JSUnusedGlobalSymbols
|
|
1007
1433
|
checkPrivateKeyValidity(dataOwner) {
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1434
|
+
var _a;
|
|
1435
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
1436
|
+
const publicKeys = Array.from(new Set([dataOwner.publicKey].concat(Object.keys((_a = dataOwner.aesExchangeKeys) !== null && _a !== void 0 ? _a : {})).filter((x) => !!x)));
|
|
1437
|
+
return yield publicKeys.reduce((pres, publicKey) => __awaiter(this, void 0, void 0, function* () {
|
|
1438
|
+
const res = yield pres;
|
|
1439
|
+
if (res) {
|
|
1440
|
+
return true;
|
|
1441
|
+
}
|
|
1442
|
+
try {
|
|
1443
|
+
const k = yield this._RSA.importKey('jwk', (0, utils_1.spkiToJwk)((0, binary_utils_1.hex2ua)(publicKey)), ['encrypt']);
|
|
1444
|
+
const cipher = yield this._RSA.encrypt(k, (0, binary_utils_1.utf8_2ua)('shibboleth'));
|
|
1445
|
+
const kp = this.loadKeyPairNotImported(dataOwner.id, publicKey.slice(-32));
|
|
1446
|
+
const plainText = yield this._RSA
|
|
1447
|
+
.importKeyPair('jwk', kp.privateKey, 'jwk', kp.publicKey)
|
|
1448
|
+
.then((ikp) => this._RSA.decrypt(ikp.privateKey, new Uint8Array(cipher)))
|
|
1449
|
+
.then((x) => (0, binary_utils_1.ua2utf8)(x));
|
|
1450
|
+
return plainText === 'shibboleth';
|
|
1451
|
+
}
|
|
1452
|
+
catch (e) {
|
|
1453
|
+
return false;
|
|
1454
|
+
}
|
|
1455
|
+
}), Promise.resolve(false));
|
|
1024
1456
|
});
|
|
1025
1457
|
}
|
|
1026
1458
|
throwDetailedExceptionForInvalidParameter(argName, argValue, methodName, methodArgs) {
|
|
@@ -1039,18 +1471,23 @@ class IccCryptoXApi {
|
|
|
1039
1471
|
throw '### THIS SHOULD NOT HAPPEN: ' + argName + ' has an invalid value: ' + argValue + details;
|
|
1040
1472
|
}
|
|
1041
1473
|
getEncryptionDecryptionKeys(dataOwnerId, document) {
|
|
1042
|
-
return
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
1474
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
1475
|
+
try {
|
|
1476
|
+
return !document.id
|
|
1477
|
+
? undefined
|
|
1478
|
+
: _.uniq((yield this.extractKeysFromDelegationsForHcpHierarchy(dataOwnerId, document.id, (document.encryptionKeys && Object.keys(document.encryptionKeys).length && document.encryptionKeys) || document.delegations)).extractedKeys);
|
|
1479
|
+
}
|
|
1480
|
+
catch (e) {
|
|
1481
|
+
return undefined;
|
|
1482
|
+
}
|
|
1483
|
+
});
|
|
1047
1484
|
}
|
|
1048
1485
|
encryptDecrypt(method, content, edKey, user, documentObject) {
|
|
1049
1486
|
return __awaiter(this, void 0, void 0, function* () {
|
|
1050
1487
|
if (!content || !(edKey || ((user === null || user === void 0 ? void 0 : user.healthcarePartyId) && documentObject)))
|
|
1051
1488
|
return content;
|
|
1052
1489
|
if (edKey) {
|
|
1053
|
-
const importedEdKey = yield this._AES.importKey('raw', binary_utils_1.hex2ua(edKey.replace(/-/g, '')));
|
|
1490
|
+
const importedEdKey = yield this._AES.importKey('raw', (0, binary_utils_1.hex2ua)(edKey.replace(/-/g, '')));
|
|
1054
1491
|
try {
|
|
1055
1492
|
return this._AES[method](importedEdKey, content);
|
|
1056
1493
|
}
|
|
@@ -1059,7 +1496,7 @@ class IccCryptoXApi {
|
|
|
1059
1496
|
}
|
|
1060
1497
|
}
|
|
1061
1498
|
const sfks = yield this.extractKeysFromDelegationsForHcpHierarchy(user === null || user === void 0 ? void 0 : user.healthcarePartyId, documentObject === null || documentObject === void 0 ? void 0 : documentObject.id, documentObject === null || documentObject === void 0 ? void 0 : documentObject.encryptionKeys);
|
|
1062
|
-
const importedEdKey = yield this._AES.importKey('raw', binary_utils_1.hex2ua(sfks.extractedKeys[0].replace(/-/g, '')));
|
|
1499
|
+
const importedEdKey = yield this._AES.importKey('raw', (0, binary_utils_1.hex2ua)(sfks.extractedKeys[0].replace(/-/g, '')));
|
|
1063
1500
|
try {
|
|
1064
1501
|
return this._AES[method](importedEdKey, content);
|
|
1065
1502
|
}
|