@protontech/drive-sdk 0.6.1 → 0.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/featureFlags.d.ts +7 -0
- package/dist/featureFlags.js +14 -0
- package/dist/featureFlags.js.map +1 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +3 -1
- package/dist/index.js.map +1 -1
- package/dist/interface/featureFlags.d.ts +7 -0
- package/dist/interface/featureFlags.js +3 -0
- package/dist/interface/featureFlags.js.map +1 -0
- package/dist/interface/index.d.ts +3 -0
- package/dist/interface/index.js.map +1 -1
- package/dist/internal/apiService/apiService.d.ts +2 -2
- package/dist/internal/apiService/apiService.js.map +1 -1
- package/dist/internal/apiService/errors.js +4 -3
- package/dist/internal/apiService/errors.js.map +1 -1
- package/dist/internal/download/cryptoService.js +8 -6
- package/dist/internal/download/cryptoService.js.map +1 -1
- package/dist/internal/download/fileDownloader.d.ts +2 -1
- package/dist/internal/download/fileDownloader.js +6 -3
- package/dist/internal/download/fileDownloader.js.map +1 -1
- package/dist/internal/download/index.d.ts +1 -1
- package/dist/internal/download/index.js +3 -3
- package/dist/internal/download/index.js.map +1 -1
- package/dist/internal/nodes/apiService.d.ts +9 -8
- package/dist/internal/nodes/apiService.js +14 -5
- package/dist/internal/nodes/apiService.js.map +1 -1
- package/dist/internal/nodes/apiService.test.js +5 -5
- package/dist/internal/nodes/apiService.test.js.map +1 -1
- package/dist/internal/nodes/cryptoReporter.d.ts +3 -3
- package/dist/internal/nodes/cryptoReporter.js.map +1 -1
- package/dist/internal/nodes/cryptoService.d.ts +12 -21
- package/dist/internal/nodes/cryptoService.js +45 -14
- package/dist/internal/nodes/cryptoService.js.map +1 -1
- package/dist/internal/nodes/cryptoService.test.js +262 -17
- package/dist/internal/nodes/cryptoService.test.js.map +1 -1
- package/dist/internal/nodes/interface.d.ts +13 -3
- package/dist/internal/nodes/nodesAccess.d.ts +8 -1
- package/dist/internal/nodes/nodesAccess.js +13 -0
- package/dist/internal/nodes/nodesAccess.js.map +1 -1
- package/dist/internal/nodes/nodesManagement.d.ts +4 -4
- package/dist/internal/nodes/nodesManagement.js +16 -20
- package/dist/internal/nodes/nodesManagement.js.map +1 -1
- package/dist/internal/nodes/nodesManagement.test.js +21 -10
- package/dist/internal/nodes/nodesManagement.test.js.map +1 -1
- package/dist/internal/photos/upload.d.ts +2 -2
- package/dist/internal/photos/upload.js +1 -1
- package/dist/internal/photos/upload.js.map +1 -1
- package/dist/internal/sharingPublic/index.d.ts +6 -6
- package/dist/internal/sharingPublic/index.js +8 -7
- package/dist/internal/sharingPublic/index.js.map +1 -1
- package/dist/internal/sharingPublic/nodes.d.ts +16 -3
- package/dist/internal/sharingPublic/nodes.js +34 -2
- package/dist/internal/sharingPublic/nodes.js.map +1 -1
- package/dist/internal/sharingPublic/unauthApiService.d.ts +17 -0
- package/dist/internal/sharingPublic/unauthApiService.js +31 -0
- package/dist/internal/sharingPublic/unauthApiService.js.map +1 -0
- package/dist/internal/sharingPublic/unauthApiService.test.d.ts +1 -0
- package/dist/internal/sharingPublic/unauthApiService.test.js +27 -0
- package/dist/internal/sharingPublic/unauthApiService.test.js.map +1 -0
- package/dist/internal/upload/apiService.d.ts +4 -3
- package/dist/internal/upload/apiService.js.map +1 -1
- package/dist/internal/upload/cryptoService.d.ts +8 -3
- package/dist/internal/upload/cryptoService.js +45 -9
- package/dist/internal/upload/cryptoService.js.map +1 -1
- package/dist/internal/upload/fileUploader.d.ts +5 -2
- package/dist/internal/upload/fileUploader.js +7 -4
- package/dist/internal/upload/fileUploader.js.map +1 -1
- package/dist/internal/upload/fileUploader.test.js +1 -1
- package/dist/internal/upload/fileUploader.test.js.map +1 -1
- package/dist/internal/upload/interface.d.ts +25 -13
- package/dist/internal/upload/manager.js +7 -4
- package/dist/internal/upload/manager.js.map +1 -1
- package/dist/internal/upload/manager.test.js +5 -4
- package/dist/internal/upload/manager.test.js.map +1 -1
- package/dist/internal/upload/streamUploader.js +9 -4
- package/dist/internal/upload/streamUploader.js.map +1 -1
- package/dist/internal/upload/streamUploader.test.js +8 -5
- package/dist/internal/upload/streamUploader.test.js.map +1 -1
- package/dist/protonDriveClient.d.ts +2 -2
- package/dist/protonDriveClient.js +7 -2
- package/dist/protonDriveClient.js.map +1 -1
- package/dist/protonDrivePublicLinkClient.d.ts +2 -1
- package/dist/protonDrivePublicLinkClient.js +7 -5
- package/dist/protonDrivePublicLinkClient.js.map +1 -1
- package/package.json +1 -1
- package/src/featureFlags.ts +11 -0
- package/src/index.ts +1 -0
- package/src/interface/featureFlags.ts +7 -0
- package/src/interface/index.ts +3 -0
- package/src/internal/apiService/apiService.ts +2 -2
- package/src/internal/apiService/errors.ts +5 -4
- package/src/internal/download/cryptoService.ts +13 -6
- package/src/internal/download/fileDownloader.ts +4 -2
- package/src/internal/download/index.ts +3 -0
- package/src/internal/nodes/apiService.test.ts +5 -5
- package/src/internal/nodes/apiService.ts +23 -10
- package/src/internal/nodes/cryptoReporter.ts +3 -3
- package/src/internal/nodes/cryptoService.test.ts +370 -18
- package/src/internal/nodes/cryptoService.ts +73 -32
- package/src/internal/nodes/interface.ts +16 -2
- package/src/internal/nodes/nodesAccess.ts +17 -0
- package/src/internal/nodes/nodesManagement.test.ts +21 -10
- package/src/internal/nodes/nodesManagement.ts +20 -24
- package/src/internal/photos/upload.ts +3 -3
- package/src/internal/sharingPublic/index.ts +7 -3
- package/src/internal/sharingPublic/nodes.ts +43 -2
- package/src/internal/sharingPublic/unauthApiService.test.ts +29 -0
- package/src/internal/sharingPublic/unauthApiService.ts +32 -0
- package/src/internal/upload/apiService.ts +4 -3
- package/src/internal/upload/cryptoService.ts +73 -12
- package/src/internal/upload/fileUploader.test.ts +1 -1
- package/src/internal/upload/fileUploader.ts +18 -11
- package/src/internal/upload/interface.ts +24 -13
- package/src/internal/upload/manager.test.ts +5 -4
- package/src/internal/upload/manager.ts +7 -4
- package/src/internal/upload/streamUploader.test.ts +8 -5
- package/src/internal/upload/streamUploader.ts +10 -4
- package/src/protonDriveClient.ts +12 -2
- package/src/protonDrivePublicLinkClient.ts +8 -3
|
@@ -1,7 +1,14 @@
|
|
|
1
1
|
import { DriveCrypto, PrivateKey, SessionKey, VERIFICATION_STATUS } from '../../crypto';
|
|
2
2
|
import { MemberRole, ProtonDriveAccount, ProtonDriveTelemetry, RevisionState } from '../../interface';
|
|
3
3
|
import { getMockTelemetry } from '../../tests/telemetry';
|
|
4
|
-
import {
|
|
4
|
+
import {
|
|
5
|
+
DecryptedNode,
|
|
6
|
+
DecryptedNodeKeys,
|
|
7
|
+
DecryptedUnparsedNode,
|
|
8
|
+
EncryptedNode,
|
|
9
|
+
NodeSigningKeys,
|
|
10
|
+
SharesService,
|
|
11
|
+
} from './interface';
|
|
5
12
|
import { NodesCryptoService } from './cryptoService';
|
|
6
13
|
import { NodesCryptoReporter } from './cryptoReporter';
|
|
7
14
|
|
|
@@ -250,6 +257,48 @@ describe('nodesCryptoService', () => {
|
|
|
250
257
|
});
|
|
251
258
|
});
|
|
252
259
|
|
|
260
|
+
it('on older node name ignores NOT_SIGNED', async () => {
|
|
261
|
+
encryptedNode.creationTime = new Date('2020-12-31');
|
|
262
|
+
driveCrypto.decryptNodeName = jest.fn(async () =>
|
|
263
|
+
Promise.resolve({
|
|
264
|
+
name: 'name',
|
|
265
|
+
verified: VERIFICATION_STATUS.NOT_SIGNED,
|
|
266
|
+
verificationErrors: [new Error('missing signature')],
|
|
267
|
+
}),
|
|
268
|
+
);
|
|
269
|
+
|
|
270
|
+
const result = await cryptoService.decryptNode(encryptedNode, parentKey);
|
|
271
|
+
verifyResult(result, {
|
|
272
|
+
nameAuthor: {
|
|
273
|
+
ok: true,
|
|
274
|
+
value: 'nameSignatureEmail',
|
|
275
|
+
},
|
|
276
|
+
});
|
|
277
|
+
expect(telemetry.recordMetric).not.toHaveBeenCalled();
|
|
278
|
+
});
|
|
279
|
+
|
|
280
|
+
it('on newer node name does not ignore NOT_SIGNED', async () => {
|
|
281
|
+
encryptedNode.creationTime = new Date('2021-01-01');
|
|
282
|
+
driveCrypto.decryptNodeName = jest.fn(async () =>
|
|
283
|
+
Promise.resolve({
|
|
284
|
+
name: 'name',
|
|
285
|
+
verified: VERIFICATION_STATUS.NOT_SIGNED,
|
|
286
|
+
verificationErrors: [new Error('missing signature')],
|
|
287
|
+
}),
|
|
288
|
+
);
|
|
289
|
+
|
|
290
|
+
const result = await cryptoService.decryptNode(encryptedNode, parentKey);
|
|
291
|
+
verifyResult(result, {
|
|
292
|
+
nameAuthor: {
|
|
293
|
+
ok: false,
|
|
294
|
+
error: {
|
|
295
|
+
claimedAuthor: 'nameSignatureEmail',
|
|
296
|
+
error: 'Missing signature for name',
|
|
297
|
+
},
|
|
298
|
+
},
|
|
299
|
+
});
|
|
300
|
+
});
|
|
301
|
+
|
|
253
302
|
it('on hash key', async () => {
|
|
254
303
|
driveCrypto.decryptNodeHashKey = jest.fn(async () =>
|
|
255
304
|
Promise.resolve({
|
|
@@ -275,6 +324,48 @@ describe('nodesCryptoService', () => {
|
|
|
275
324
|
});
|
|
276
325
|
});
|
|
277
326
|
|
|
327
|
+
it('on older node hash key ignores NOT_SIGNED', async () => {
|
|
328
|
+
encryptedNode.creationTime = new Date('2021-07-31');
|
|
329
|
+
driveCrypto.decryptNodeHashKey = jest.fn(async () =>
|
|
330
|
+
Promise.resolve({
|
|
331
|
+
hashKey: new Uint8Array(),
|
|
332
|
+
verified: VERIFICATION_STATUS.NOT_SIGNED,
|
|
333
|
+
verificationErrors: [new Error('missing signature')],
|
|
334
|
+
}),
|
|
335
|
+
);
|
|
336
|
+
|
|
337
|
+
const result = await cryptoService.decryptNode(encryptedNode, parentKey);
|
|
338
|
+
verifyResult(result, {
|
|
339
|
+
keyAuthor: {
|
|
340
|
+
ok: true,
|
|
341
|
+
value: 'signatureEmail',
|
|
342
|
+
},
|
|
343
|
+
});
|
|
344
|
+
expect(telemetry.recordMetric).not.toHaveBeenCalled();
|
|
345
|
+
});
|
|
346
|
+
|
|
347
|
+
it('on newer node hash key does not ignore NOT_SIGNED', async () => {
|
|
348
|
+
encryptedNode.creationTime = new Date('2021-08-01');
|
|
349
|
+
driveCrypto.decryptNodeHashKey = jest.fn(async () =>
|
|
350
|
+
Promise.resolve({
|
|
351
|
+
hashKey: new Uint8Array(),
|
|
352
|
+
verified: VERIFICATION_STATUS.NOT_SIGNED,
|
|
353
|
+
verificationErrors: [new Error('missing signature')],
|
|
354
|
+
}),
|
|
355
|
+
);
|
|
356
|
+
|
|
357
|
+
const result = await cryptoService.decryptNode(encryptedNode, parentKey);
|
|
358
|
+
verifyResult(result, {
|
|
359
|
+
keyAuthor: {
|
|
360
|
+
ok: false,
|
|
361
|
+
error: {
|
|
362
|
+
claimedAuthor: 'signatureEmail',
|
|
363
|
+
error: 'Missing signature for hash key',
|
|
364
|
+
},
|
|
365
|
+
},
|
|
366
|
+
});
|
|
367
|
+
});
|
|
368
|
+
|
|
278
369
|
it('on node key and hash key reports error from node key', async () => {
|
|
279
370
|
driveCrypto.decryptKey = jest.fn(async () =>
|
|
280
371
|
Promise.resolve({
|
|
@@ -985,24 +1076,239 @@ describe('nodesCryptoService', () => {
|
|
|
985
1076
|
});
|
|
986
1077
|
});
|
|
987
1078
|
|
|
1079
|
+
describe('createFolder', () => {
|
|
1080
|
+
let parentKeys: any;
|
|
1081
|
+
|
|
1082
|
+
beforeEach(() => {
|
|
1083
|
+
parentKeys = {
|
|
1084
|
+
key: 'parentKey' as any,
|
|
1085
|
+
hashKey: new Uint8Array([1, 2, 3]),
|
|
1086
|
+
};
|
|
1087
|
+
driveCrypto.generateKey = jest.fn().mockResolvedValue({
|
|
1088
|
+
encrypted: {
|
|
1089
|
+
armoredKey: 'encryptedNodeKey',
|
|
1090
|
+
armoredPassphrase: 'encryptedPassphrase',
|
|
1091
|
+
armoredPassphraseSignature: 'passphraseSignature',
|
|
1092
|
+
},
|
|
1093
|
+
decrypted: {
|
|
1094
|
+
key: 'nodeKey' as any,
|
|
1095
|
+
passphrase: 'nodePassphrase',
|
|
1096
|
+
passphraseSessionKey: 'passphraseSessionKey' as any,
|
|
1097
|
+
},
|
|
1098
|
+
});
|
|
1099
|
+
driveCrypto.encryptNodeName = jest.fn().mockResolvedValue({
|
|
1100
|
+
armoredNodeName: 'encryptedNodeName',
|
|
1101
|
+
});
|
|
1102
|
+
driveCrypto.generateLookupHash = jest.fn().mockResolvedValue('lookupHash');
|
|
1103
|
+
driveCrypto.generateHashKey = jest.fn().mockResolvedValue({
|
|
1104
|
+
armoredHashKey: 'encryptedHashKey',
|
|
1105
|
+
hashKey: new Uint8Array([4, 5, 6]),
|
|
1106
|
+
});
|
|
1107
|
+
driveCrypto.encryptExtendedAttributes = jest.fn().mockResolvedValue({
|
|
1108
|
+
armoredExtendedAttributes: 'encryptedAttributes',
|
|
1109
|
+
});
|
|
1110
|
+
});
|
|
1111
|
+
|
|
1112
|
+
it('should encrypt new folder with account key', async () => {
|
|
1113
|
+
const signingKeys: NodeSigningKeys = {
|
|
1114
|
+
type: 'userAddress',
|
|
1115
|
+
email: 'test@example.com',
|
|
1116
|
+
addressId: 'addressId',
|
|
1117
|
+
key: 'addressKey' as any,
|
|
1118
|
+
};
|
|
1119
|
+
|
|
1120
|
+
const result = await cryptoService.createFolder(
|
|
1121
|
+
parentKeys,
|
|
1122
|
+
signingKeys,
|
|
1123
|
+
'New Folder',
|
|
1124
|
+
'{"modificationTime": 1234567890}',
|
|
1125
|
+
);
|
|
1126
|
+
|
|
1127
|
+
expect(result).toEqual({
|
|
1128
|
+
encryptedCrypto: {
|
|
1129
|
+
encryptedName: 'encryptedNodeName',
|
|
1130
|
+
hash: 'lookupHash',
|
|
1131
|
+
armoredKey: 'encryptedNodeKey',
|
|
1132
|
+
armoredNodePassphrase: 'encryptedPassphrase',
|
|
1133
|
+
armoredNodePassphraseSignature: 'passphraseSignature',
|
|
1134
|
+
folder: {
|
|
1135
|
+
armoredExtendedAttributes: 'encryptedAttributes',
|
|
1136
|
+
armoredHashKey: 'encryptedHashKey',
|
|
1137
|
+
},
|
|
1138
|
+
signatureEmail: 'test@example.com',
|
|
1139
|
+
nameSignatureEmail: 'test@example.com',
|
|
1140
|
+
},
|
|
1141
|
+
keys: {
|
|
1142
|
+
passphrase: 'nodePassphrase',
|
|
1143
|
+
key: 'nodeKey',
|
|
1144
|
+
passphraseSessionKey: 'passphraseSessionKey',
|
|
1145
|
+
hashKey: new Uint8Array([4, 5, 6]),
|
|
1146
|
+
},
|
|
1147
|
+
});
|
|
1148
|
+
|
|
1149
|
+
expect(driveCrypto.generateKey).toHaveBeenCalledWith([parentKeys.key], signingKeys.key);
|
|
1150
|
+
expect(driveCrypto.encryptNodeName).toHaveBeenCalledWith(
|
|
1151
|
+
'New Folder',
|
|
1152
|
+
undefined,
|
|
1153
|
+
parentKeys.key,
|
|
1154
|
+
signingKeys.key,
|
|
1155
|
+
);
|
|
1156
|
+
expect(driveCrypto.generateLookupHash).toHaveBeenCalledWith('New Folder', parentKeys.hashKey);
|
|
1157
|
+
expect(driveCrypto.generateHashKey).toHaveBeenCalledWith('nodeKey');
|
|
1158
|
+
expect(driveCrypto.encryptExtendedAttributes).toHaveBeenCalledWith(
|
|
1159
|
+
'{"modificationTime": 1234567890}',
|
|
1160
|
+
'nodeKey',
|
|
1161
|
+
signingKeys.key,
|
|
1162
|
+
);
|
|
1163
|
+
});
|
|
1164
|
+
|
|
1165
|
+
it('should encrypt new folder with node key', async () => {
|
|
1166
|
+
const signingKeys: NodeSigningKeys = {
|
|
1167
|
+
type: 'nodeKey',
|
|
1168
|
+
nodeKey: 'nodeSigningKey' as any,
|
|
1169
|
+
parentNodeKey: 'parentNodeKey' as any,
|
|
1170
|
+
};
|
|
1171
|
+
|
|
1172
|
+
const result = await cryptoService.createFolder(
|
|
1173
|
+
parentKeys,
|
|
1174
|
+
signingKeys,
|
|
1175
|
+
'New Folder',
|
|
1176
|
+
'{"modificationTime": 1234567890}',
|
|
1177
|
+
);
|
|
1178
|
+
|
|
1179
|
+
expect(result).toEqual({
|
|
1180
|
+
encryptedCrypto: {
|
|
1181
|
+
encryptedName: 'encryptedNodeName',
|
|
1182
|
+
hash: 'lookupHash',
|
|
1183
|
+
armoredKey: 'encryptedNodeKey',
|
|
1184
|
+
armoredNodePassphrase: 'encryptedPassphrase',
|
|
1185
|
+
armoredNodePassphraseSignature: 'passphraseSignature',
|
|
1186
|
+
folder: {
|
|
1187
|
+
armoredExtendedAttributes: 'encryptedAttributes',
|
|
1188
|
+
armoredHashKey: 'encryptedHashKey',
|
|
1189
|
+
},
|
|
1190
|
+
signatureEmail: null,
|
|
1191
|
+
nameSignatureEmail: null,
|
|
1192
|
+
},
|
|
1193
|
+
keys: {
|
|
1194
|
+
passphrase: 'nodePassphrase',
|
|
1195
|
+
key: 'nodeKey',
|
|
1196
|
+
passphraseSessionKey: 'passphraseSessionKey',
|
|
1197
|
+
hashKey: new Uint8Array([4, 5, 6]),
|
|
1198
|
+
},
|
|
1199
|
+
});
|
|
1200
|
+
|
|
1201
|
+
expect(driveCrypto.generateKey).toHaveBeenCalledWith([parentKeys.key], signingKeys.parentNodeKey);
|
|
1202
|
+
expect(driveCrypto.encryptNodeName).toHaveBeenCalledWith(
|
|
1203
|
+
'New Folder',
|
|
1204
|
+
undefined,
|
|
1205
|
+
parentKeys.key,
|
|
1206
|
+
signingKeys.parentNodeKey,
|
|
1207
|
+
);
|
|
1208
|
+
expect(driveCrypto.generateLookupHash).toHaveBeenCalledWith('New Folder', parentKeys.hashKey);
|
|
1209
|
+
expect(driveCrypto.generateHashKey).toHaveBeenCalledWith('nodeKey');
|
|
1210
|
+
expect(driveCrypto.encryptExtendedAttributes).toHaveBeenCalledWith(
|
|
1211
|
+
'{"modificationTime": 1234567890}',
|
|
1212
|
+
'nodeKey',
|
|
1213
|
+
signingKeys.nodeKey,
|
|
1214
|
+
);
|
|
1215
|
+
});
|
|
1216
|
+
});
|
|
1217
|
+
|
|
1218
|
+
describe('encryptNewName', () => {
|
|
1219
|
+
let parentKeys: any;
|
|
1220
|
+
let nodeNameSessionKey: SessionKey;
|
|
1221
|
+
|
|
1222
|
+
beforeEach(() => {
|
|
1223
|
+
parentKeys = {
|
|
1224
|
+
key: 'parentKey' as any,
|
|
1225
|
+
hashKey: new Uint8Array([1, 2, 3]),
|
|
1226
|
+
};
|
|
1227
|
+
nodeNameSessionKey = 'nameSessionKey' as any;
|
|
1228
|
+
driveCrypto.encryptNodeName = jest.fn().mockResolvedValue({
|
|
1229
|
+
armoredNodeName: 'encryptedNewNodeName',
|
|
1230
|
+
});
|
|
1231
|
+
driveCrypto.generateLookupHash = jest.fn().mockResolvedValue('newHash');
|
|
1232
|
+
});
|
|
1233
|
+
|
|
1234
|
+
it('should encrypt new name with account key', async () => {
|
|
1235
|
+
const signingKeys: NodeSigningKeys = {
|
|
1236
|
+
type: 'userAddress',
|
|
1237
|
+
email: 'test@example.com',
|
|
1238
|
+
addressId: 'addressId',
|
|
1239
|
+
key: 'addressKey' as any,
|
|
1240
|
+
};
|
|
1241
|
+
|
|
1242
|
+
const result = await cryptoService.encryptNewName(
|
|
1243
|
+
parentKeys,
|
|
1244
|
+
nodeNameSessionKey,
|
|
1245
|
+
signingKeys,
|
|
1246
|
+
'Renamed File.txt',
|
|
1247
|
+
);
|
|
1248
|
+
|
|
1249
|
+
expect(result).toEqual({
|
|
1250
|
+
signatureEmail: 'test@example.com',
|
|
1251
|
+
armoredNodeName: 'encryptedNewNodeName',
|
|
1252
|
+
hash: 'newHash',
|
|
1253
|
+
});
|
|
1254
|
+
|
|
1255
|
+
expect(driveCrypto.encryptNodeName).toHaveBeenCalledWith(
|
|
1256
|
+
'Renamed File.txt',
|
|
1257
|
+
nodeNameSessionKey,
|
|
1258
|
+
parentKeys.key,
|
|
1259
|
+
signingKeys.key,
|
|
1260
|
+
);
|
|
1261
|
+
expect(driveCrypto.generateLookupHash).toHaveBeenCalledWith('Renamed File.txt', parentKeys.hashKey);
|
|
1262
|
+
});
|
|
1263
|
+
|
|
1264
|
+
it('should encrypt new name with node key', async () => {
|
|
1265
|
+
const signingKeys: NodeSigningKeys = {
|
|
1266
|
+
type: 'nodeKey',
|
|
1267
|
+
nodeKey: 'nodeSigningKey' as any,
|
|
1268
|
+
parentNodeKey: 'parentNodeKey' as any,
|
|
1269
|
+
};
|
|
1270
|
+
|
|
1271
|
+
const result = await cryptoService.encryptNewName(
|
|
1272
|
+
parentKeys,
|
|
1273
|
+
nodeNameSessionKey,
|
|
1274
|
+
signingKeys,
|
|
1275
|
+
'Renamed File.txt',
|
|
1276
|
+
);
|
|
1277
|
+
|
|
1278
|
+
expect(result).toEqual({
|
|
1279
|
+
signatureEmail: null,
|
|
1280
|
+
armoredNodeName: 'encryptedNewNodeName',
|
|
1281
|
+
hash: 'newHash',
|
|
1282
|
+
});
|
|
1283
|
+
|
|
1284
|
+
expect(driveCrypto.encryptNodeName).toHaveBeenCalledWith(
|
|
1285
|
+
'Renamed File.txt',
|
|
1286
|
+
nodeNameSessionKey,
|
|
1287
|
+
parentKeys.key,
|
|
1288
|
+
signingKeys.parentNodeKey,
|
|
1289
|
+
);
|
|
1290
|
+
expect(driveCrypto.generateLookupHash).toHaveBeenCalledWith('Renamed File.txt', parentKeys.hashKey);
|
|
1291
|
+
});
|
|
1292
|
+
});
|
|
1293
|
+
|
|
988
1294
|
describe('encryptNodeWithNewParent', () => {
|
|
989
|
-
|
|
990
|
-
|
|
1295
|
+
let node: DecryptedNode;
|
|
1296
|
+
let keys: any;
|
|
1297
|
+
let parentKeys: any;
|
|
1298
|
+
|
|
1299
|
+
beforeEach(() => {
|
|
1300
|
+
node = {
|
|
991
1301
|
name: { ok: true, value: 'testFile.txt' },
|
|
992
1302
|
} as DecryptedNode;
|
|
993
|
-
|
|
1303
|
+
keys = {
|
|
994
1304
|
passphrase: 'nodePassphrase',
|
|
995
1305
|
passphraseSessionKey: 'nodePassphraseSessionKey',
|
|
996
1306
|
nameSessionKey: 'nameSessionKey' as any,
|
|
997
1307
|
};
|
|
998
|
-
|
|
1308
|
+
parentKeys = {
|
|
999
1309
|
key: 'newParentKey' as any,
|
|
1000
1310
|
hashKey: new Uint8Array([1, 2, 3]),
|
|
1001
1311
|
};
|
|
1002
|
-
const address = {
|
|
1003
|
-
email: 'test@example.com',
|
|
1004
|
-
addressKey: 'addressKey' as any,
|
|
1005
|
-
};
|
|
1006
1312
|
driveCrypto.encryptNodeName = jest.fn().mockResolvedValue({
|
|
1007
1313
|
armoredNodeName: 'encryptedNodeName',
|
|
1008
1314
|
});
|
|
@@ -1011,8 +1317,17 @@ describe('nodesCryptoService', () => {
|
|
|
1011
1317
|
armoredPassphrase: 'encryptedPassphrase',
|
|
1012
1318
|
armoredPassphraseSignature: 'passphraseSignature',
|
|
1013
1319
|
});
|
|
1320
|
+
});
|
|
1014
1321
|
|
|
1015
|
-
|
|
1322
|
+
it('should encrypt node data for move operation with account key (logged in context)', async () => {
|
|
1323
|
+
const signingKeys: NodeSigningKeys = {
|
|
1324
|
+
type: 'userAddress',
|
|
1325
|
+
email: 'test@example.com',
|
|
1326
|
+
addressId: 'addressId',
|
|
1327
|
+
key: 'addressKey' as any,
|
|
1328
|
+
};
|
|
1329
|
+
|
|
1330
|
+
const result = await cryptoService.encryptNodeWithNewParent(node, keys as any, parentKeys, signingKeys);
|
|
1016
1331
|
|
|
1017
1332
|
expect(result).toEqual({
|
|
1018
1333
|
encryptedName: 'encryptedNodeName',
|
|
@@ -1027,14 +1342,47 @@ describe('nodesCryptoService', () => {
|
|
|
1027
1342
|
'testFile.txt',
|
|
1028
1343
|
keys.nameSessionKey,
|
|
1029
1344
|
parentKeys.key,
|
|
1030
|
-
|
|
1345
|
+
signingKeys.key,
|
|
1346
|
+
);
|
|
1347
|
+
expect(driveCrypto.generateLookupHash).toHaveBeenCalledWith('testFile.txt', parentKeys.hashKey);
|
|
1348
|
+
expect(driveCrypto.encryptPassphrase).toHaveBeenCalledWith(
|
|
1349
|
+
keys.passphrase,
|
|
1350
|
+
keys.passphraseSessionKey,
|
|
1351
|
+
[parentKeys.key],
|
|
1352
|
+
signingKeys.key,
|
|
1353
|
+
);
|
|
1354
|
+
});
|
|
1355
|
+
|
|
1356
|
+
it('should encrypt node data for move operation with node key (anonymous context)', async () => {
|
|
1357
|
+
const signingKeys: NodeSigningKeys = {
|
|
1358
|
+
type: 'nodeKey',
|
|
1359
|
+
nodeKey: 'addressKey' as any,
|
|
1360
|
+
parentNodeKey: 'parentNodeKey' as any,
|
|
1361
|
+
};
|
|
1362
|
+
|
|
1363
|
+
const result = await cryptoService.encryptNodeWithNewParent(node, keys as any, parentKeys, signingKeys);
|
|
1364
|
+
|
|
1365
|
+
expect(result).toEqual({
|
|
1366
|
+
encryptedName: 'encryptedNodeName',
|
|
1367
|
+
hash: 'newHash',
|
|
1368
|
+
armoredNodePassphrase: 'encryptedPassphrase',
|
|
1369
|
+
armoredNodePassphraseSignature: 'passphraseSignature',
|
|
1370
|
+
signatureEmail: null,
|
|
1371
|
+
nameSignatureEmail: null,
|
|
1372
|
+
});
|
|
1373
|
+
|
|
1374
|
+
expect(driveCrypto.encryptNodeName).toHaveBeenCalledWith(
|
|
1375
|
+
'testFile.txt',
|
|
1376
|
+
keys.nameSessionKey,
|
|
1377
|
+
parentKeys.key,
|
|
1378
|
+
signingKeys.nodeKey,
|
|
1031
1379
|
);
|
|
1032
1380
|
expect(driveCrypto.generateLookupHash).toHaveBeenCalledWith('testFile.txt', parentKeys.hashKey);
|
|
1033
1381
|
expect(driveCrypto.encryptPassphrase).toHaveBeenCalledWith(
|
|
1034
1382
|
keys.passphrase,
|
|
1035
1383
|
keys.passphraseSessionKey,
|
|
1036
1384
|
[parentKeys.key],
|
|
1037
|
-
|
|
1385
|
+
signingKeys.nodeKey,
|
|
1038
1386
|
);
|
|
1039
1387
|
});
|
|
1040
1388
|
|
|
@@ -1051,13 +1399,15 @@ describe('nodesCryptoService', () => {
|
|
|
1051
1399
|
key: 'newParentKey' as any,
|
|
1052
1400
|
hashKey: undefined,
|
|
1053
1401
|
} as any;
|
|
1054
|
-
const
|
|
1402
|
+
const signingKeys: NodeSigningKeys = {
|
|
1403
|
+
type: 'userAddress',
|
|
1055
1404
|
email: 'test@example.com',
|
|
1056
|
-
|
|
1405
|
+
addressId: 'addressId',
|
|
1406
|
+
key: 'addressKey' as any,
|
|
1057
1407
|
};
|
|
1058
1408
|
|
|
1059
1409
|
await expect(
|
|
1060
|
-
cryptoService.encryptNodeWithNewParent(node, keys as any, parentKeys,
|
|
1410
|
+
cryptoService.encryptNodeWithNewParent(node, keys as any, parentKeys, signingKeys),
|
|
1061
1411
|
).rejects.toThrow('Moving item to a non-folder is not allowed');
|
|
1062
1412
|
});
|
|
1063
1413
|
|
|
@@ -1074,13 +1424,15 @@ describe('nodesCryptoService', () => {
|
|
|
1074
1424
|
key: 'newParentKey' as any,
|
|
1075
1425
|
hashKey: new Uint8Array([1, 2, 3]),
|
|
1076
1426
|
};
|
|
1077
|
-
const
|
|
1427
|
+
const signingKeys: NodeSigningKeys = {
|
|
1428
|
+
type: 'userAddress',
|
|
1078
1429
|
email: 'test@example.com',
|
|
1079
|
-
|
|
1430
|
+
addressId: 'addressId',
|
|
1431
|
+
key: 'addressKey' as any,
|
|
1080
1432
|
};
|
|
1081
1433
|
|
|
1082
1434
|
await expect(
|
|
1083
|
-
cryptoService.encryptNodeWithNewParent(node, keys as any, parentKeys,
|
|
1435
|
+
cryptoService.encryptNodeWithNewParent(node, keys as any, parentKeys, signingKeys),
|
|
1084
1436
|
).rejects.toThrow('Cannot move item without a valid name, please rename the item first');
|
|
1085
1437
|
});
|
|
1086
1438
|
});
|
|
@@ -24,6 +24,7 @@ import {
|
|
|
24
24
|
DecryptedNodeKeys,
|
|
25
25
|
EncryptedRevision,
|
|
26
26
|
DecryptedUnparsedRevision,
|
|
27
|
+
NodeSigningKeys,
|
|
27
28
|
} from './interface';
|
|
28
29
|
|
|
29
30
|
export interface NodesCryptoReporter {
|
|
@@ -33,7 +34,7 @@ export interface NodesCryptoReporter {
|
|
|
33
34
|
signatureType: string,
|
|
34
35
|
verified: VERIFICATION_STATUS,
|
|
35
36
|
verificationErrors?: Error[],
|
|
36
|
-
claimedAuthor?: string,
|
|
37
|
+
claimedAuthor?: string | AnonymousUser,
|
|
37
38
|
notAvailableVerificationKeys?: boolean,
|
|
38
39
|
): Promise<Author>;
|
|
39
40
|
reportDecryptionError(node: NodesCryptoReporterNode, field: MetricsDecryptionErrorField, error: unknown): void;
|
|
@@ -336,11 +337,19 @@ export class NodesCryptoService {
|
|
|
336
337
|
const nameSignatureEmail = node.encryptedCrypto.nameSignatureEmail;
|
|
337
338
|
|
|
338
339
|
try {
|
|
339
|
-
const {
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
);
|
|
340
|
+
const {
|
|
341
|
+
name,
|
|
342
|
+
verified: verificationStatus,
|
|
343
|
+
verificationErrors,
|
|
344
|
+
} = await this.driveCrypto.decryptNodeName(node.encryptedName, parentKey, verificationKeys);
|
|
345
|
+
|
|
346
|
+
let verified = verificationStatus;
|
|
347
|
+
// The name was not signed until Drive web Beta 3.
|
|
348
|
+
// It is decided to ignore this and consider it signed.
|
|
349
|
+
// The problem will be gone with migration to new crypto model.
|
|
350
|
+
if (verificationStatus === VERIFICATION_STATUS.NOT_SIGNED && node.creationTime < new Date(2021, 0, 1)) {
|
|
351
|
+
verified = VERIFICATION_STATUS.SIGNED_AND_VALID;
|
|
352
|
+
}
|
|
344
353
|
|
|
345
354
|
return {
|
|
346
355
|
name: resultOk(name),
|
|
@@ -438,11 +447,19 @@ export class NodesCryptoService {
|
|
|
438
447
|
throw new Error('Node is not a folder');
|
|
439
448
|
}
|
|
440
449
|
|
|
441
|
-
const {
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
);
|
|
450
|
+
const {
|
|
451
|
+
hashKey,
|
|
452
|
+
verified: verificationStatus,
|
|
453
|
+
verificationErrors,
|
|
454
|
+
} = await this.driveCrypto.decryptNodeHashKey(node.encryptedCrypto.folder.armoredHashKey, nodeKey, addressKeys);
|
|
455
|
+
|
|
456
|
+
let verified = verificationStatus;
|
|
457
|
+
// The hash was not signed until Drive web Beta 17.
|
|
458
|
+
// It is decided to ignore this and consider it signed.
|
|
459
|
+
// The problem will be gone with migration to new crypto model.
|
|
460
|
+
if (verificationStatus === VERIFICATION_STATUS.NOT_SIGNED && node.creationTime < new Date(2021, 7, 1)) {
|
|
461
|
+
verified = VERIFICATION_STATUS.SIGNED_AND_VALID;
|
|
462
|
+
}
|
|
446
463
|
|
|
447
464
|
return {
|
|
448
465
|
hashKey,
|
|
@@ -490,7 +507,7 @@ export class NodesCryptoService {
|
|
|
490
507
|
encryptedExtendedAttributes: string | undefined,
|
|
491
508
|
nodeKey: PrivateKey,
|
|
492
509
|
addressKeys: PublicKey[],
|
|
493
|
-
signatureEmail?: string,
|
|
510
|
+
signatureEmail?: string | AnonymousUser,
|
|
494
511
|
): Promise<{
|
|
495
512
|
extendedAttributes?: string;
|
|
496
513
|
author: Author;
|
|
@@ -522,31 +539,44 @@ export class NodesCryptoService {
|
|
|
522
539
|
|
|
523
540
|
async createFolder(
|
|
524
541
|
parentKeys: { key: PrivateKey; hashKey: Uint8Array },
|
|
525
|
-
|
|
542
|
+
signingKeys: NodeSigningKeys,
|
|
526
543
|
name: string,
|
|
527
544
|
extendedAttributes?: string,
|
|
528
545
|
): Promise<{
|
|
529
|
-
encryptedCrypto: EncryptedNodeFolderCrypto & {
|
|
546
|
+
encryptedCrypto: Omit<EncryptedNodeFolderCrypto, 'signatureEmail' | 'nameSignatureEmail'> & {
|
|
547
|
+
signatureEmail: string | AnonymousUser;
|
|
548
|
+
nameSignatureEmail: string | AnonymousUser;
|
|
530
549
|
armoredNodePassphraseSignature: string;
|
|
531
|
-
// signatureEmail and nameSignatureEmail are not optional.
|
|
532
|
-
signatureEmail: string;
|
|
533
|
-
nameSignatureEmail: string;
|
|
534
550
|
encryptedName: string;
|
|
535
551
|
hash: string;
|
|
536
552
|
};
|
|
537
553
|
keys: DecryptedNodeKeys;
|
|
538
554
|
}> {
|
|
539
|
-
const
|
|
555
|
+
const email = signingKeys.type === 'userAddress' ? signingKeys.email : null;
|
|
556
|
+
const nameAndPassprhaseSigningKey =
|
|
557
|
+
signingKeys.type === 'userAddress' ? signingKeys.key : signingKeys.parentNodeKey;
|
|
558
|
+
if (!nameAndPassprhaseSigningKey) {
|
|
559
|
+
// This is a bug within the SDK.
|
|
560
|
+
throw new Error('Cannot create new node without a name and passphrase signing key');
|
|
561
|
+
}
|
|
562
|
+
|
|
540
563
|
const [nodeKeys, { armoredNodeName }, hash] = await Promise.all([
|
|
541
|
-
this.driveCrypto.generateKey([parentKeys.key],
|
|
542
|
-
this.driveCrypto.encryptNodeName(name, undefined, parentKeys.key,
|
|
564
|
+
this.driveCrypto.generateKey([parentKeys.key], nameAndPassprhaseSigningKey),
|
|
565
|
+
this.driveCrypto.encryptNodeName(name, undefined, parentKeys.key, nameAndPassprhaseSigningKey),
|
|
543
566
|
this.driveCrypto.generateLookupHash(name, parentKeys.hashKey),
|
|
544
567
|
]);
|
|
545
568
|
|
|
546
569
|
const { armoredHashKey, hashKey } = await this.driveCrypto.generateHashKey(nodeKeys.decrypted.key);
|
|
547
570
|
|
|
571
|
+
const extendedAttributesSigningKey =
|
|
572
|
+
signingKeys.type === 'userAddress' ? signingKeys.key : signingKeys.nodeKey || nodeKeys.decrypted.key;
|
|
573
|
+
|
|
548
574
|
const { armoredExtendedAttributes } = extendedAttributes
|
|
549
|
-
? await this.driveCrypto.encryptExtendedAttributes(
|
|
575
|
+
? await this.driveCrypto.encryptExtendedAttributes(
|
|
576
|
+
extendedAttributes,
|
|
577
|
+
nodeKeys.decrypted.key,
|
|
578
|
+
extendedAttributesSigningKey,
|
|
579
|
+
)
|
|
550
580
|
: { armoredExtendedAttributes: undefined };
|
|
551
581
|
|
|
552
582
|
return {
|
|
@@ -575,20 +605,25 @@ export class NodesCryptoService {
|
|
|
575
605
|
async encryptNewName(
|
|
576
606
|
parentKeys: { key: PrivateKey; hashKey?: Uint8Array },
|
|
577
607
|
nodeNameSessionKey: SessionKey,
|
|
578
|
-
|
|
608
|
+
signingKeys: NodeSigningKeys,
|
|
579
609
|
newName: string,
|
|
580
610
|
): Promise<{
|
|
581
|
-
signatureEmail: string;
|
|
611
|
+
signatureEmail: string | AnonymousUser;
|
|
582
612
|
armoredNodeName: string;
|
|
583
613
|
hash?: string;
|
|
584
614
|
}> {
|
|
585
|
-
const
|
|
615
|
+
const email = signingKeys.type === 'userAddress' ? signingKeys.email : null;
|
|
616
|
+
const nameSigningKey = signingKeys.type === 'userAddress' ? signingKeys.key : signingKeys.parentNodeKey;
|
|
617
|
+
if (!nameSigningKey) {
|
|
618
|
+
// This is a bug within the SDK.
|
|
619
|
+
throw new Error('Cannot encrypt new node name without a name signing key');
|
|
620
|
+
}
|
|
586
621
|
|
|
587
622
|
const { armoredNodeName } = await this.driveCrypto.encryptNodeName(
|
|
588
623
|
newName,
|
|
589
624
|
nodeNameSessionKey,
|
|
590
625
|
parentKeys.key,
|
|
591
|
-
|
|
626
|
+
nameSigningKey,
|
|
592
627
|
);
|
|
593
628
|
|
|
594
629
|
const hash = parentKeys.hashKey
|
|
@@ -605,14 +640,14 @@ export class NodesCryptoService {
|
|
|
605
640
|
node: Pick<DecryptedNode, 'name'>,
|
|
606
641
|
keys: { passphrase: string; passphraseSessionKey: SessionKey; nameSessionKey: SessionKey },
|
|
607
642
|
parentKeys: { key: PrivateKey; hashKey: Uint8Array },
|
|
608
|
-
|
|
643
|
+
signingKeys: NodeSigningKeys,
|
|
609
644
|
): Promise<{
|
|
610
645
|
encryptedName: string;
|
|
611
646
|
hash: string;
|
|
612
647
|
armoredNodePassphrase: string;
|
|
613
648
|
armoredNodePassphraseSignature: string;
|
|
614
|
-
signatureEmail: string;
|
|
615
|
-
nameSignatureEmail: string;
|
|
649
|
+
signatureEmail: string | AnonymousUser;
|
|
650
|
+
nameSignatureEmail: string | AnonymousUser;
|
|
616
651
|
}> {
|
|
617
652
|
if (!parentKeys.hashKey) {
|
|
618
653
|
throw new ValidationError('Moving item to a non-folder is not allowed');
|
|
@@ -621,19 +656,25 @@ export class NodesCryptoService {
|
|
|
621
656
|
throw new ValidationError('Cannot move item without a valid name, please rename the item first');
|
|
622
657
|
}
|
|
623
658
|
|
|
624
|
-
const
|
|
659
|
+
const email = signingKeys.type === 'userAddress' ? signingKeys.email : null;
|
|
660
|
+
const nameAndPassprhaseSigningKey = signingKeys.type === 'userAddress' ? signingKeys.key : signingKeys.nodeKey;
|
|
661
|
+
if (!nameAndPassprhaseSigningKey) {
|
|
662
|
+
// This is a bug within the SDK.
|
|
663
|
+
throw new Error('Cannot re-encrypt node without a name and passphrase signing key');
|
|
664
|
+
}
|
|
665
|
+
|
|
625
666
|
const { armoredNodeName } = await this.driveCrypto.encryptNodeName(
|
|
626
667
|
node.name.value,
|
|
627
668
|
keys.nameSessionKey,
|
|
628
669
|
parentKeys.key,
|
|
629
|
-
|
|
670
|
+
nameAndPassprhaseSigningKey,
|
|
630
671
|
);
|
|
631
672
|
const hash = await this.driveCrypto.generateLookupHash(node.name.value, parentKeys.hashKey);
|
|
632
673
|
const { armoredPassphrase, armoredPassphraseSignature } = await this.driveCrypto.encryptPassphrase(
|
|
633
674
|
keys.passphrase,
|
|
634
675
|
keys.passphraseSessionKey,
|
|
635
676
|
[parentKeys.key],
|
|
636
|
-
|
|
677
|
+
nameAndPassprhaseSigningKey,
|
|
637
678
|
);
|
|
638
679
|
|
|
639
680
|
return {
|
|
@@ -657,7 +698,7 @@ export class NodesCryptoService {
|
|
|
657
698
|
}
|
|
658
699
|
|
|
659
700
|
function getClaimedAuthor(
|
|
660
|
-
claimedAuthor?: string,
|
|
701
|
+
claimedAuthor?: string | AnonymousUser,
|
|
661
702
|
notAvailableVerificationKeys = false,
|
|
662
703
|
): string | AnonymousUser | undefined {
|
|
663
704
|
if (!claimedAuthor && notAvailableVerificationKeys) {
|