@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.
Files changed (119) hide show
  1. package/dist/featureFlags.d.ts +7 -0
  2. package/dist/featureFlags.js +14 -0
  3. package/dist/featureFlags.js.map +1 -0
  4. package/dist/index.d.ts +1 -0
  5. package/dist/index.js +3 -1
  6. package/dist/index.js.map +1 -1
  7. package/dist/interface/featureFlags.d.ts +7 -0
  8. package/dist/interface/featureFlags.js +3 -0
  9. package/dist/interface/featureFlags.js.map +1 -0
  10. package/dist/interface/index.d.ts +3 -0
  11. package/dist/interface/index.js.map +1 -1
  12. package/dist/internal/apiService/apiService.d.ts +2 -2
  13. package/dist/internal/apiService/apiService.js.map +1 -1
  14. package/dist/internal/apiService/errors.js +4 -3
  15. package/dist/internal/apiService/errors.js.map +1 -1
  16. package/dist/internal/download/cryptoService.js +8 -6
  17. package/dist/internal/download/cryptoService.js.map +1 -1
  18. package/dist/internal/download/fileDownloader.d.ts +2 -1
  19. package/dist/internal/download/fileDownloader.js +6 -3
  20. package/dist/internal/download/fileDownloader.js.map +1 -1
  21. package/dist/internal/download/index.d.ts +1 -1
  22. package/dist/internal/download/index.js +3 -3
  23. package/dist/internal/download/index.js.map +1 -1
  24. package/dist/internal/nodes/apiService.d.ts +9 -8
  25. package/dist/internal/nodes/apiService.js +14 -5
  26. package/dist/internal/nodes/apiService.js.map +1 -1
  27. package/dist/internal/nodes/apiService.test.js +5 -5
  28. package/dist/internal/nodes/apiService.test.js.map +1 -1
  29. package/dist/internal/nodes/cryptoReporter.d.ts +3 -3
  30. package/dist/internal/nodes/cryptoReporter.js.map +1 -1
  31. package/dist/internal/nodes/cryptoService.d.ts +12 -21
  32. package/dist/internal/nodes/cryptoService.js +45 -14
  33. package/dist/internal/nodes/cryptoService.js.map +1 -1
  34. package/dist/internal/nodes/cryptoService.test.js +262 -17
  35. package/dist/internal/nodes/cryptoService.test.js.map +1 -1
  36. package/dist/internal/nodes/interface.d.ts +13 -3
  37. package/dist/internal/nodes/nodesAccess.d.ts +8 -1
  38. package/dist/internal/nodes/nodesAccess.js +13 -0
  39. package/dist/internal/nodes/nodesAccess.js.map +1 -1
  40. package/dist/internal/nodes/nodesManagement.d.ts +4 -4
  41. package/dist/internal/nodes/nodesManagement.js +16 -20
  42. package/dist/internal/nodes/nodesManagement.js.map +1 -1
  43. package/dist/internal/nodes/nodesManagement.test.js +21 -10
  44. package/dist/internal/nodes/nodesManagement.test.js.map +1 -1
  45. package/dist/internal/photos/upload.d.ts +2 -2
  46. package/dist/internal/photos/upload.js +1 -1
  47. package/dist/internal/photos/upload.js.map +1 -1
  48. package/dist/internal/sharingPublic/index.d.ts +6 -6
  49. package/dist/internal/sharingPublic/index.js +8 -7
  50. package/dist/internal/sharingPublic/index.js.map +1 -1
  51. package/dist/internal/sharingPublic/nodes.d.ts +16 -3
  52. package/dist/internal/sharingPublic/nodes.js +34 -2
  53. package/dist/internal/sharingPublic/nodes.js.map +1 -1
  54. package/dist/internal/sharingPublic/unauthApiService.d.ts +17 -0
  55. package/dist/internal/sharingPublic/unauthApiService.js +31 -0
  56. package/dist/internal/sharingPublic/unauthApiService.js.map +1 -0
  57. package/dist/internal/sharingPublic/unauthApiService.test.d.ts +1 -0
  58. package/dist/internal/sharingPublic/unauthApiService.test.js +27 -0
  59. package/dist/internal/sharingPublic/unauthApiService.test.js.map +1 -0
  60. package/dist/internal/upload/apiService.d.ts +4 -3
  61. package/dist/internal/upload/apiService.js.map +1 -1
  62. package/dist/internal/upload/cryptoService.d.ts +8 -3
  63. package/dist/internal/upload/cryptoService.js +45 -9
  64. package/dist/internal/upload/cryptoService.js.map +1 -1
  65. package/dist/internal/upload/fileUploader.d.ts +5 -2
  66. package/dist/internal/upload/fileUploader.js +7 -4
  67. package/dist/internal/upload/fileUploader.js.map +1 -1
  68. package/dist/internal/upload/fileUploader.test.js +1 -1
  69. package/dist/internal/upload/fileUploader.test.js.map +1 -1
  70. package/dist/internal/upload/interface.d.ts +25 -13
  71. package/dist/internal/upload/manager.js +7 -4
  72. package/dist/internal/upload/manager.js.map +1 -1
  73. package/dist/internal/upload/manager.test.js +5 -4
  74. package/dist/internal/upload/manager.test.js.map +1 -1
  75. package/dist/internal/upload/streamUploader.js +9 -4
  76. package/dist/internal/upload/streamUploader.js.map +1 -1
  77. package/dist/internal/upload/streamUploader.test.js +8 -5
  78. package/dist/internal/upload/streamUploader.test.js.map +1 -1
  79. package/dist/protonDriveClient.d.ts +2 -2
  80. package/dist/protonDriveClient.js +7 -2
  81. package/dist/protonDriveClient.js.map +1 -1
  82. package/dist/protonDrivePublicLinkClient.d.ts +2 -1
  83. package/dist/protonDrivePublicLinkClient.js +7 -5
  84. package/dist/protonDrivePublicLinkClient.js.map +1 -1
  85. package/package.json +1 -1
  86. package/src/featureFlags.ts +11 -0
  87. package/src/index.ts +1 -0
  88. package/src/interface/featureFlags.ts +7 -0
  89. package/src/interface/index.ts +3 -0
  90. package/src/internal/apiService/apiService.ts +2 -2
  91. package/src/internal/apiService/errors.ts +5 -4
  92. package/src/internal/download/cryptoService.ts +13 -6
  93. package/src/internal/download/fileDownloader.ts +4 -2
  94. package/src/internal/download/index.ts +3 -0
  95. package/src/internal/nodes/apiService.test.ts +5 -5
  96. package/src/internal/nodes/apiService.ts +23 -10
  97. package/src/internal/nodes/cryptoReporter.ts +3 -3
  98. package/src/internal/nodes/cryptoService.test.ts +370 -18
  99. package/src/internal/nodes/cryptoService.ts +73 -32
  100. package/src/internal/nodes/interface.ts +16 -2
  101. package/src/internal/nodes/nodesAccess.ts +17 -0
  102. package/src/internal/nodes/nodesManagement.test.ts +21 -10
  103. package/src/internal/nodes/nodesManagement.ts +20 -24
  104. package/src/internal/photos/upload.ts +3 -3
  105. package/src/internal/sharingPublic/index.ts +7 -3
  106. package/src/internal/sharingPublic/nodes.ts +43 -2
  107. package/src/internal/sharingPublic/unauthApiService.test.ts +29 -0
  108. package/src/internal/sharingPublic/unauthApiService.ts +32 -0
  109. package/src/internal/upload/apiService.ts +4 -3
  110. package/src/internal/upload/cryptoService.ts +73 -12
  111. package/src/internal/upload/fileUploader.test.ts +1 -1
  112. package/src/internal/upload/fileUploader.ts +18 -11
  113. package/src/internal/upload/interface.ts +24 -13
  114. package/src/internal/upload/manager.test.ts +5 -4
  115. package/src/internal/upload/manager.ts +7 -4
  116. package/src/internal/upload/streamUploader.test.ts +8 -5
  117. package/src/internal/upload/streamUploader.ts +10 -4
  118. package/src/protonDriveClient.ts +12 -2
  119. 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 { DecryptedNode, DecryptedNodeKeys, DecryptedUnparsedNode, EncryptedNode, SharesService } from './interface';
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
- it('should encrypt node data for move operation', async () => {
990
- const node = {
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
- const keys = {
1303
+ keys = {
994
1304
  passphrase: 'nodePassphrase',
995
1305
  passphraseSessionKey: 'nodePassphraseSessionKey',
996
1306
  nameSessionKey: 'nameSessionKey' as any,
997
1307
  };
998
- const parentKeys = {
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
- const result = await cryptoService.encryptNodeWithNewParent(node, keys as any, parentKeys, address);
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
- address.addressKey,
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
- address.addressKey,
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 address = {
1402
+ const signingKeys: NodeSigningKeys = {
1403
+ type: 'userAddress',
1055
1404
  email: 'test@example.com',
1056
- addressKey: 'addressKey' as any,
1405
+ addressId: 'addressId',
1406
+ key: 'addressKey' as any,
1057
1407
  };
1058
1408
 
1059
1409
  await expect(
1060
- cryptoService.encryptNodeWithNewParent(node, keys as any, parentKeys, address),
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 address = {
1427
+ const signingKeys: NodeSigningKeys = {
1428
+ type: 'userAddress',
1078
1429
  email: 'test@example.com',
1079
- addressKey: 'addressKey' as any,
1430
+ addressId: 'addressId',
1431
+ key: 'addressKey' as any,
1080
1432
  };
1081
1433
 
1082
1434
  await expect(
1083
- cryptoService.encryptNodeWithNewParent(node, keys as any, parentKeys, address),
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 { name, verified, verificationErrors } = await this.driveCrypto.decryptNodeName(
340
- node.encryptedName,
341
- parentKey,
342
- verificationKeys,
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 { hashKey, verified, verificationErrors } = await this.driveCrypto.decryptNodeHashKey(
442
- node.encryptedCrypto.folder.armoredHashKey,
443
- nodeKey,
444
- addressKeys,
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
- address: { email: string; addressKey: PrivateKey },
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 { email, addressKey } = address;
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], addressKey),
542
- this.driveCrypto.encryptNodeName(name, undefined, parentKeys.key, addressKey),
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(extendedAttributes, nodeKeys.decrypted.key, addressKey)
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
- address: { email: string; addressKey: PrivateKey },
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 { email, addressKey } = address;
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
- addressKey,
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
- address: { email: string; addressKey: PrivateKey },
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 { email, addressKey } = address;
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
- addressKey,
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
- addressKey,
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) {