-
Notifications
You must be signed in to change notification settings - Fork 53
Expand file tree
/
Copy pathunit-node.ts
More file actions
2873 lines (2722 loc) · 178 KB
/
unit-node.ts
File metadata and controls
2873 lines (2722 loc) · 178 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
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
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
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
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
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
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
/* ©️ 2016 - present FlowCrypt a.s. Limitations apply. Contact human@flowcrypt.com */
import test from 'ava';
import { MsgBlock } from '../core/msg-block';
import { MsgBlockParser } from '../core/msg-block-parser';
import { Config, TestVariant } from '../util';
import { use, expect } from 'chai';
import chaiAsPromised from 'chai-as-promised';
import { KeyUtil, KeyInfoWithIdentityAndOptionalPp, Key } from '../core/crypto/key';
import { UnreportableError } from '../platform/error-report.js';
import { Buf } from '../core/buf';
import { OpenPGPKey } from '../core/crypto/pgp/openpgp-key';
import { DecryptError, DecryptSuccess, MsgUtil } from '../core/crypto/pgp/msg-util';
import { opgp } from '../core/crypto/pgp/openpgpjs-custom';
import { Attachment } from '../core/attachment.js';
import { GoogleData, GmailMsg } from '../mock/google/google-data';
import { testConstants } from './tooling/consts';
import { PgpArmor } from '../core/crypto/pgp/pgp-armor';
import { readFileSync } from 'fs';
import * as forge from 'node-forge';
import { ENVELOPED_DATA_OID, SmimeKey } from '../core/crypto/smime/smime-key';
import { Str } from '../core/common';
import { PgpPwd } from '../core/crypto/pgp/pgp-password';
use(chaiAsPromised);
export const equals = (a: string | Uint8Array, b: string | Uint8Array) => {
expect(typeof a).to.equal(typeof b, `types dont match`);
if (typeof a === 'string' && typeof b === 'string') {
expect(a).to.equal(b, 'string result mismatch');
return;
}
if (a instanceof Uint8Array && b instanceof Uint8Array) {
expect(Array.from(a).join('|')).to.equal(Array.from(b).join('|'), 'buffers dont match');
return;
}
throw new Error(`unknown test state [${typeof a},${typeof b}] [${a instanceof Uint8Array},${b instanceof Uint8Array}]`);
};
export const defineUnitNodeTests = (testVariant: TestVariant) => {
if (testVariant !== 'CONSUMER-LIVE-GMAIL') {
test(`[unit][KeyUtil.parse] throw if parse methods expecting exactly one key find more than one`, async t => {
const unarmoredKeys = Buffer.from([
...(await PgpArmor.dearmor(testConstants.flowcryptcompatibilityPublicKey7FDE685548AEA788)).data,
...(await PgpArmor.dearmor(testConstants.pubkey2864E326A5BE488A)).data,
]);
const armoredKeys = PgpArmor.armor(opgp.enums.armor.publicKey, unarmoredKeys);
expect((await KeyUtil.parseMany(armoredKeys)).length).to.equal(2);
await t.throwsAsync(() => OpenPGPKey.parse(armoredKeys), {
instanceOf: Error,
message: 'Found 2 OpenPGP keys, expected one',
});
await t.throwsAsync(() => KeyUtil.parse(armoredKeys), {
instanceOf: Error,
message: 'Found 2 keys, expected one',
});
t.pass();
});
test(`[unit][OpenPGPKey.parseMany] throws on invalid input`, async t => {
await t.throwsAsync(
() =>
OpenPGPKey.parseMany(`-----BEGIN PGP PUBLIC KEY BLOCK-----
Version: FlowCrypt Email Encryption
Comment: Seamlessly send and receive encrypted email
Something wrong with this key`),
{
instanceOf: Error,
message: 'Misformed armored text',
}
);
});
test(`[unit][KeyUtil.parseMany] throws on invalid input`, async t => {
await t.throwsAsync(
() =>
KeyUtil.parseMany(`-----BEGIN PGP PUBLIC KEY BLOCK-----
Version: FlowCrypt Email Encryption
Comment: Seamlessly send and receive encrypted email
Something wrong with this key`),
{
instanceOf: Error,
message: 'Misformed armored text',
}
);
});
test(`[unit][OpenPGPKey.parse] throws on invalid input`, async t => {
await t.throwsAsync(
() =>
OpenPGPKey.parse(`-----BEGIN PGP PUBLIC KEY BLOCK-----
Version: FlowCrypt Email Encryption
Comment: Seamlessly send and receive encrypted email
Something wrong with this key`),
{
instanceOf: Error,
message: 'Misformed armored text',
}
);
});
test(`[unit][KeyUtil.parse] throws on invalid input`, async t => {
await t.throwsAsync(
() =>
KeyUtil.parse(`-----BEGIN PGP PUBLIC KEY BLOCK-----
Version: FlowCrypt Email Encryption
Comment: Seamlessly send and receive encrypted email
Something wrong with this key`),
{
instanceOf: Error,
message: 'Misformed armored text',
}
);
});
test(`[unit][OpenPGPKey.getOrCreateRevocationCertificate] operations`, async t => {
const stringData = 'hello';
const data = Buf.fromUtfStr(stringData);
const originalPrv = await OpenPGPKey.parse(testConstants.existingPrv);
const revocationCertificate = await OpenPGPKey.getOrCreateRevocationCertificate(originalPrv);
expect(revocationCertificate).to.be.not.empty;
if (!revocationCertificate) {
throw new Error();
}
expect(revocationCertificate.startsWith('-----BEGIN PGP PUBLIC KEY BLOCK-----')).to.be.true;
expect(revocationCertificate).to.include('Version: FlowCrypt Email Encryption');
expect(revocationCertificate).to.include('Comment: Seamlessly send and receive encrypted email');
expect(revocationCertificate).to.include('Comment: This is a revocation certificate');
const expectNotRevoked = async (key: Key) => {
expect(key.revoked).to.be.false;
if (key.isPrivate) {
await MsgUtil.sign(originalPrv, stringData);
} else {
await MsgUtil.encryptMessage({ pubkeys: [pubkey], data, armor: true });
}
};
const expectRevoked = async (key: Key) => {
expect(key.revoked).to.be.true;
if (key.isPrivate) {
await t.throwsAsync(() => MsgUtil.sign(revokedPrv, stringData), {
instanceOf: Error,
message: 'Error signing message: Primary key is revoked',
});
} else {
await t.throwsAsync(() => MsgUtil.encryptMessage({ pubkeys: [revokedPub], data, armor: true }), {
instanceOf: Error,
message: 'Error encrypting message: Primary key is revoked',
});
}
};
const testKey = async (key: Key, func: (key: Key) => Promise<void>) => {
await func(key);
const armored = KeyUtil.armor(key);
const unarmored = await OpenPGPKey.parse(armored);
await func(unarmored);
KeyUtil.pack(key);
await func(key);
};
await testKey(originalPrv, expectNotRevoked); // the original key remains valid
const pubkey = await KeyUtil.asPublicKey(originalPrv);
await testKey(pubkey, expectNotRevoked); // the pub key remains valid
await t.throwsAsync(() => OpenPGPKey.getOrCreateRevocationCertificate(pubkey), {
instanceOf: Error,
message: 'Key FAFB7D675AC74E87F84D169F00B0115807969D75 is not a private key',
});
// apply revocation certificate
const revokedPrv = await OpenPGPKey.applyRevocationCertificate(originalPrv, revocationCertificate);
await testKey(originalPrv, expectNotRevoked); // the original key remains valid
await testKey(revokedPrv, expectRevoked);
const revokedPub = await OpenPGPKey.applyRevocationCertificate(pubkey, revocationCertificate);
await testKey(pubkey, expectNotRevoked); // the original key remains valid
await testKey(revokedPub, expectRevoked);
// extract the same revocation certificate from the revoked keys
expect(await KeyUtil.getOrCreateRevocationCertificate(revokedPub)).to.equal(revocationCertificate);
expect(await KeyUtil.getOrCreateRevocationCertificate(revokedPrv)).to.equal(revocationCertificate);
});
test(`[unit][MsgBlockParser.detectBlocks] does not get tripped on blocks with unknown headers`, async t => {
expect(
MsgBlockParser.detectBlocks("This text breaks email and Gmail web app.\n\n-----BEGIN FOO-----\n\nEven though it's not a vaild PGP m\n\nMuhahah")
).to.deep.equal({
blocks: [
MsgBlock.fromContent(
'plainText',
"This text breaks email and Gmail web app.\n\n-----BEGIN FOO-----\n\nEven though it's not a vaild PGP m\n\nMuhahah"
),
],
normalized: "This text breaks email and Gmail web app.\n\n-----BEGIN FOO-----\n\nEven though it's not a vaild PGP m\n\nMuhahah",
});
t.pass();
});
test(`[unit][MsgBlockParser.detectBlocks] ignores false-positive blocks`, async t => {
const input = `Hello, sending you the promised json:
{
"entries" : [ {
"id" : "1,email-key-manager,evaluation.org,pgp-key-private,106988520142055188323",
"content" : "-----BEGIN PGP PRIVATE KEY BLOCK-----\r\nVersion: FlowCrypt 7.6.9 Gmail Encryption\r\nComment: Seamlessly send and receive encrypted email\r\n\r\nxcLYBF5mRKEBCADX62s0p6mI6yrxB/ui/LqxfG4RcQzZJf8ah52Ynu1n8V7Y\r\n7143LmT3MfCDw1bfHu2k1OK7hT+BOi6sXas1D/fVtjz5WwuoBvwf1DBZ7eq8\r\ntMQbLqQ7m/A8uwrVFOhWfuxulM7RuzIPIgv4HqtKKEugprUd80bPus45+f80\r\nH6ZSgEpmZD6t9JShY6f8pU1OHcnPqFsFF0sLyOk7WcCG5Li3WjkwU/lIu18q\r\nR26oLb5UM8z6vv6JD29GmqCj+OLYaPk8b00kdpGEvTjw3VzGM+tXOgUf2y1T\r\nK9UfhMNkyswxUZw543CMTdw9V0+AzM0q70T/p0fP9nlJCv6M3bQm6D/vABEB\r\nAAEAB/sG3UWhvWjO4QcS9ZmC43z98oI/TLRHXQVgrwoMFZVflhVZWTbKE1AD\r\nadOHJNkoq7+LW3c/1esgbRyZvzqXq8PJyArlNIdI1rwCOQk2erFZQXfwk0mG\r\nWZ1IGPwtrQX75foXQ+TVVxmu0HrH7xWr/F73IwWkB51rMjmnLzL1UcJEYh/I\r\nVS5a4+KhCHf4k7GNewLdTd74ERNfL/BPRS2vye4oxJCr9Qx2nwB9a8WMk7X4\r\nIYIH0zpo5/Eu5nXUZyZ2D/72UlOmsox376J8B4lkoRMQPmIvfLBqyX4w7EG6\r\ngwBF+gib/hyHm8aAgkwPs931CDDJNf0wq17dqbDN0Uk8q1SRBADtHbjT2Utl\r\ns6R0g8BRakCh4FT1t/fvlFXO14T0O28vfGroWtbd0q/2XJF1WcRU9NXdo2DG\r\n3z5dQJzKz/nb8G9/LDpWcuBfYWXT3YZVOSiIUSp9SwYGTHIXCxqYev+ALc1b\r\nO3PYpbYgadnPeu/7qRTIzN9Wrnplp5PO7RcBGGWY/wQA6R2L8IEz1wZuiUqd\r\nFsb7Rzpe2bp4sQNsCdaX69Ci0fHsIOltku52K4A1hEqCaPZBGh7gnYGYSx2w\r\nF3UklJxaaxh3EjaxJT0R6+fHpkdhjnsKIgyhjwnuZSHQYINah00jupIZRjn7\r\n67XnOKKnWajodAojfgsdZqAbZ/WHSq8X6RED/i5Q4xaoa72VT3hMTYRkR6R9\r\nhBVjmR6NsUq9cIZoV6txFbpijj79qzrlY7yAl1NA7bkuHxvE+uHVBqFtBo2I\r\n3f9cINbCWWdgsAvNtYEwUnpgzDoL5UF0TCZvtmF2r0R7zVniuDTeKyEoUZYF\r\nJA1o6k3hnwCQDFLfWchcVPIra2pVPZrNL0VrbSBVc2VyIDxla21AZWttLW9y\r\nZy1ydWxlcy10ZXN0LmZsb3djcnlwdC5jb20+wsB1BBABCAAfBQJeZkShBgsJ\r\nBwgDAgQVCAoCAxYCAQIZAQIbAwIeAQAKCRDESadeBea4P0KvCACD5uOgGxwG\r\nEmUWfH8EXPK7npDKulmoZnSWYrfCX3ctUKXjwPBWRXYid7LChnQAR6SRcyxy\r\nD1Eoel5ZVrJyKHqRkxcanFHeqRU1OyOgtsQyPIGtLipmOgc6i5JYhqbQ4mNu\r\n10CGS6ZKhjf6rFIqLl/8f4lnBc28UqVuP20Ru6KJZTVVQRF28FweMByR/3Ly\r\nAWfObMwXJ0+uFEV941VEDv5MGdIdfePTP2cHRSJxPqVhpPWtfzYLStUzLFvt\r\nLfE45hympok4lZeKfLVtZVVQEgT+ojEImdiZQJ0dT+jeJhmuTjzURQcLapXv\r\n2GLBUZaY2zfoAXR31QNYjADOxlrOutSUx8LYBF5mRKEBCACVNQTzI2Cf1+G3\r\nq38OtXO89tuBI/a5TjcHh/sFIJB6PPuEg/uW+EsjkgI3yk+UZZd6iYohO2mJ\r\ncJ7MnaFHOu7tmOEaaHSiYsA0RTnVqUBlbHbsl2oSlQJ/mjJ4cWq5ateuLHhx\r\n2RV0t1bm2anHJnqKGkqYqXA72m5grLzRSJ9M43wQRheGWGNoNdg4kPxU+PjY\r\nwfk2ARX5SCUKoG0qp0RhRMplX74uYi+Ek/9qSyZevmhK55sXIUNwLsuEhejl\r\nr0iucOt2vcIybQ9EbMXz62yYMRjYgy4SxW5aQJxXFeWkSo6wzMqQ1ZiSArRC\r\nezBk+mftxNrmwmtCcJajQt2uAQQVABEBAAEAB/sFz/fuZM1pzKYdWo/ricQF\r\nc3RfloAQ/ewE3hY4P+mA6Yk+w0l0ux1qOFDfzYDGHiMFggAghUj6Mqns/KMA\r\nvFn8ZX03YyRQAxrLrnqvSRWaHdyQIOHf8XAUenRG3twydugJ/+99N+CvGElJ\r\nWudTO7uAT7/iLI+TtVGhcHk2ieayvwaleWfQd9eVw37xi58hMWV/NSBOIZhW\r\n2Lv/aldPr8ld8vlWYN4xbTCLF45FoetBrGjDkXb3BCELHSj/ot7I+wZ1uGIF\r\n33wh8Q0EWFgqQtMBnyL6m/XO0U1sOrJADVGQsOQ1/5+3AnpUJOHnP9rnhy8A\r\n2glYg3+2sRRupRG4n/6NBADJKA4RsHwvOeRx1pnuOD8B2fP0r5qJ4gi+tsRq\r\nIXOY1dpPbhzo4AAn+RVwo6JC3aUWtt2yUsJ9eTyWG432LkM9eUwL4Z//ymXf\r\nVFIfl4ySyEvbSujNfreEYM7FUr7kxpBfGE1c86J+AX6MZpfw9hIGs+8IHr/j\r\ngoZe8+CD+1xBuwQAveMZgrB+CoGjQMaVa6/GoWagV20KjHKXDhI/Aogjnu/B\r\nlwHemh1pJucI5kvnq+SaupFO8dgDt+bhwJxsH6d/Wj/J80+TR7pvYFSkk3LV\r\nP3IGRUy7U11LKEqno5n9/4/EuXvV/lixalIGNOGgpnoHgwPIkT9AYGxOlF21\r\n8T4nTG8D/R/URs9vxc9nmTDm9ykw0cHDMmSqLl1a5Dzl2VpQitFBgmaCEo5L\r\ne+QN/nX0KWMFttKXo++N/sU988sOhxQyEzeTq6B+9YJVnaaxAZByDRzrMgG+\r\nq/5XGxzbwsCta5NxE3iY9CWDrPm20KUkBF3ZKoDrlV0Uck6wX+XLipoDc4AX\r\nRfHCwF8EGAEIAAkFAl5mRKECGwwACgkQxEmnXgXmuD/7VAf+IMJMoADcdWNh\r\nn45AvkwbzSmYt4i2aRGe+qojswwYzvFBFZtyZ/FKV2+LHfKUBI18FRmHmKEb\r\na1UUetflytxiAwZxSJSf7Yz/NDiWaVn0eOLopmFMiPb02a5i3CjbLsDeex2y\r\n/69R0+fQc+rE3HZ04C8H/YAqFV0VOv3L+2EztOGK7KOZOx4toR05oDqbZbiD\r\nzwhsa2MugHLPLZuGl3eGk+n/EcINhopHg+HU8MHQE6rADvrok6QiYVhpGqi8\r\nksD3kBAk43hGRSD2m/WDPWa/h2sh5rVswTKUDtv1fd1H6Ff5FnK21LHjEk0f\r\n+P9DgunMb5OtkDwm6WWxpzV150LJcA==\r\n=FAco\r\n-----END PGP PRIVATE KEY BLOCK-----\r\n"
}, {
"id" : "1,email-key-manager,evaluation.org,pgp-key-public,ekm%40ekm-client-configuration-test.flowcrypt.test",
"content" : "-----BEGIN PGP PUBLIC KEY BLOCK-----\r\nVersion: FlowCrypt 7.6.9 Gmail Encryption\r\nComment: Seamlessly send and receive encrypted email\r\n\r\nxsBNBF5mRKEBCADX62s0p6mI6yrxB/ui/LqxfG4RcQzZJf8ah52Ynu1n8V7Y\r\n7143LmT3MfCDw1bfHu2k1OK7hT+BOi6sXas1D/fVtjz5WwuoBvwf1DBZ7eq8\r\ntMQbLqQ7m/A8uwrVFOhWfuxulM7RuzIPIgv4HqtKKEugprUd80bPus45+f80\r\nH6ZSgEpmZD6t9JShY6f8pU1OHcnPqFsFF0sLyOk7WcCG5Li3WjkwU/lIu18q\r\nR26oLb5UM8z6vv6JD29GmqCj+OLYaPk8b00kdpGEvTjw3VzGM+tXOgUf2y1T\r\nK9UfhMNkyswxUZw543CMTdw9V0+AzM0q70T/p0fP9nlJCv6M3bQm6D/vABEB\r\nAAHNL0VrbSBVc2VyIDxla21AZWttLW9yZy1ydWxlcy10ZXN0LmZsb3djcnlw\r\ndC5jb20+wsB1BBABCAAfBQJeZkShBgsJBwgDAgQVCAoCAxYCAQIZAQIbAwIe\r\nAQAKCRDESadeBea4P0KvCACD5uOgGxwGEmUWfH8EXPK7npDKulmoZnSWYrfC\r\nX3ctUKXjwPBWRXYid7LChnQAR6SRcyxyD1Eoel5ZVrJyKHqRkxcanFHeqRU1\r\nOyOgtsQyPIGtLipmOgc6i5JYhqbQ4mNu10CGS6ZKhjf6rFIqLl/8f4lnBc28\r\nUqVuP20Ru6KJZTVVQRF28FweMByR/3LyAWfObMwXJ0+uFEV941VEDv5MGdId\r\nfePTP2cHRSJxPqVhpPWtfzYLStUzLFvtLfE45hympok4lZeKfLVtZVVQEgT+\r\nojEImdiZQJ0dT+jeJhmuTjzURQcLapXv2GLBUZaY2zfoAXR31QNYjADOxlrO\r\nutSUzsBNBF5mRKEBCACVNQTzI2Cf1+G3q38OtXO89tuBI/a5TjcHh/sFIJB6\r\nPPuEg/uW+EsjkgI3yk+UZZd6iYohO2mJcJ7MnaFHOu7tmOEaaHSiYsA0RTnV\r\nqUBlbHbsl2oSlQJ/mjJ4cWq5ateuLHhx2RV0t1bm2anHJnqKGkqYqXA72m5g\r\nrLzRSJ9M43wQRheGWGNoNdg4kPxU+PjYwfk2ARX5SCUKoG0qp0RhRMplX74u\r\nYi+Ek/9qSyZevmhK55sXIUNwLsuEhejlr0iucOt2vcIybQ9EbMXz62yYMRjY\r\ngy4SxW5aQJxXFeWkSo6wzMqQ1ZiSArRCezBk+mftxNrmwmtCcJajQt2uAQQV\r\nABEBAAHCwF8EGAEIAAkFAl5mRKECGwwACgkQxEmnXgXmuD/7VAf+IMJMoADc\r\ndWNhn45AvkwbzSmYt4i2aRGe+qojswwYzvFBFZtyZ/FKV2+LHfKUBI18FRmH\r\nmKEba1UUetflytxiAwZxSJSf7Yz/NDiWaVn0eOLopmFMiPb02a5i3CjbLsDe\r\nex2y/69R0+fQc+rE3HZ04C8H/YAqFV0VOv3L+2EztOGK7KOZOx4toR05oDqb\r\nZbiDzwhsa2MugHLPLZuGl3eGk+n/EcINhopHg+HU8MHQE6rADvrok6QiYVhp\r\nGqi8ksD3kBAk43hGRSD2m/WDPWa/h2sh5rVswTKUDtv1fd1H6Ff5FnK21LHj\r\nEk0f+P9DgunMb5OtkDwm6WWxpzV150LJcA==\r\n=Hcoc\r\n-----END PGP PUBLIC KEY BLOCK-----\r\n"
}, {
"id" : "1,email-key-manager,evaluation.org,pgp-key-fingerprint,C05803F40E0B9FE4FE9B4822C449A75E05E6B83F",
"content" : "1,email-key-manager,evaluation.org,pgp-key-private,106988520142055188323\n1,email-key-manager,evaluation.org,pgp-key-public,ekm%40ekm-client-configuration-test.flowcrypt.test"
} ]
}`;
const { blocks, normalized } = MsgBlockParser.detectBlocks(input);
expect(normalized).to.equal(input);
expect(blocks).to.have.property('length').that.equals(1);
expect(blocks[0]).to.deep.equal(MsgBlock.fromContent('plainText', input));
t.pass();
});
test(`[unit][MsgBlockParser.detectBlocks] replaces intended blocks`, async t => {
const prv = `-----BEGIN PGP PRIVATE KEY BLOCK-----\r\nVersion: FlowCrypt 7.6.9 Gmail Encryption\r\nComment: Seamlessly send and receive encrypted email\r\n\r\n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\r\n+P9DgunMb5OtkDwm6WWxpzV150LJcA==\r\n=FAco\r\n-----END PGP PRIVATE KEY BLOCK-----`;
const pub = `-----BEGIN PGP PUBLIC KEY BLOCK-----\r\nVersion: FlowCrypt 7.6.9 Gmail Encryption\r\nComment: Seamlessly send and receive encrypted email\r\n\r\nxsBNBF5mRKEBCADX62s0p6mI6yrxB/ui/LqxfG4RcQzZJf8ah52Ynu1n8V7Y\r\n7143LmT3MfCDw1bfHu2k1OK7hT+BOi6sXas1D/fVtjz5WwuoBvwf1DBZ7eq8\r\ntMQbLqQ7m/A8uwrVFOhWfuxulM7RuzIPIgv4HqtKKEugprUd80bPus45+f80\r\nH6ZSgEpmZD6t9JShY6f8pU1OHcnPqFsFF0sLyOk7WcCG5Li3WjkwU/lIu18q\r\nR26oLb5UM8z6vv6JD29GmqCj+OLYaPk8b00kdpGEvTjw3VzGM+tXOgUf2y1T\r\nK9UfhMNkyswxUZw543CMTdw9V0+AzM0q70T/p0fP9nlJCv6M3bQm6D/vABEB\r\nAAHNL0VrbSBVc2VyIDxla21AZWttLW9yZy1ydWxlcy10ZXN0LmZsb3djcnlw\r\ndC5jb20+wsB1BBABCAAfBQJeZkShBgsJBwgDAgQVCAoCAxYCAQIZAQIbAwIe\r\nAQAKCRDESadeBea4P0KvCACD5uOgGxwGEmUWfH8EXPK7npDKulmoZnSWYrfC\r\nX3ctUKXjwPBWRXYid7LChnQAR6SRcyxyD1Eoel5ZVrJyKHqRkxcanFHeqRU1\r\nOyOgtsQyPIGtLipmOgc6i5JYhqbQ4mNu10CGS6ZKhjf6rFIqLl/8f4lnBc28\r\nUqVuP20Ru6KJZTVVQRF28FweMByR/3LyAWfObMwXJ0+uFEV941VEDv5MGdId\r\nfePTP2cHRSJxPqVhpPWtfzYLStUzLFvtLfE45hympok4lZeKfLVtZVVQEgT+\r\nojEImdiZQJ0dT+jeJhmuTjzURQcLapXv2GLBUZaY2zfoAXR31QNYjADOxlrO\r\nutSUzsBNBF5mRKEBCACVNQTzI2Cf1+G3q38OtXO89tuBI/a5TjcHh/sFIJB6\r\nPPuEg/uW+EsjkgI3yk+UZZd6iYohO2mJcJ7MnaFHOu7tmOEaaHSiYsA0RTnV\r\nqUBlbHbsl2oSlQJ/mjJ4cWq5ateuLHhx2RV0t1bm2anHJnqKGkqYqXA72m5g\r\nrLzRSJ9M43wQRheGWGNoNdg4kPxU+PjYwfk2ARX5SCUKoG0qp0RhRMplX74u\r\nYi+Ek/9qSyZevmhK55sXIUNwLsuEhejlr0iucOt2vcIybQ9EbMXz62yYMRjY\r\ngy4SxW5aQJxXFeWkSo6wzMqQ1ZiSArRCezBk+mftxNrmwmtCcJajQt2uAQQV\r\nABEBAAHCwF8EGAEIAAkFAl5mRKECGwwACgkQxEmnXgXmuD/7VAf+IMJMoADc\r\ndWNhn45AvkwbzSmYt4i2aRGe+qojswwYzvFBFZtyZ/FKV2+LHfKUBI18FRmH\r\nmKEba1UUetflytxiAwZxSJSf7Yz/NDiWaVn0eOLopmFMiPb02a5i3CjbLsDe\r\nex2y/69R0+fQc+rE3HZ04C8H/YAqFV0VOv3L+2EztOGK7KOZOx4toR05oDqb\r\nZbiDzwhsa2MugHLPLZuGl3eGk+n/EcINhopHg+HU8MHQE6rADvrok6QiYVhp\r\nGqi8ksD3kBAk43hGRSD2m/WDPWa/h2sh5rVswTKUDtv1fd1H6Ff5FnK21LHj\r\nEk0f+P9DgunMb5OtkDwm6WWxpzV150LJcA==\r\n=Hcoc\r\n-----END PGP PUBLIC KEY BLOCK-----`;
const input = `Hello, these should get replaced:\n${prv}\n\nAnd this one too:\n\n${pub}`;
const { blocks, normalized } = MsgBlockParser.detectBlocks(input);
expect(normalized).to.equal(input);
expect(blocks).to.have.property('length').that.equals(4);
expect(blocks[0]).to.deep.equal(MsgBlock.fromContent('plainText', 'Hello, these should get replaced:'));
expect(blocks[1]).to.deep.equal(MsgBlock.fromContent('privateKey', prv));
expect(blocks[2]).to.deep.equal(MsgBlock.fromContent('plainText', 'And this one too:'));
expect(blocks[3]).to.deep.equal(MsgBlock.fromContent('publicKey', pub));
t.pass();
});
test(`[unit][MsgBlockParser.detectBlocks] correctly handles nested quoted signed messages`, async t => {
// This tests the fix for the bug where a signed message containing a quoted signed message
// would cause "Misformed armored text" error. The parser should find the correct (outer,
// non-quoted) END PGP SIGNATURE marker instead of the quoted inner one.
const nestedSignedMessage = `-----BEGIN PGP SIGNED MESSAGE-----
Hash: SHA512
This is the outer message.
On 2026-01-15, someone@example.com wrote:
> -----BEGIN PGP SIGNED MESSAGE-----
> Hash: SHA512
>
> This is the quoted inner message.
>
> -----BEGIN PGP SIGNATURE-----
> quotedSignatureData
> -----END PGP SIGNATURE-----
-----BEGIN PGP SIGNATURE-----
outerSignatureData
-----END PGP SIGNATURE-----`;
const { blocks } = MsgBlockParser.detectBlocks(nestedSignedMessage);
expect(blocks).to.have.length(1);
expect(blocks[0].type).to.equal('signedMsg');
// The block should contain the entire message including the quoted inner signature
expect(blocks[0].content).to.include('outerSignatureData');
expect(blocks[0].content).to.include('quotedSignatureData');
expect(blocks[0].content).to.include('-----BEGIN PGP SIGNED MESSAGE-----');
// Verify it ends with the outer signature, not the quoted one
expect(blocks[0].content).to.match(/outerSignatureData\n-----END PGP SIGNATURE-----$/);
t.pass();
});
test(`[unit][PgpKey.usableForEncryptionButExpired] recognizes usable expired key`, async t => {
const expiredKey = await KeyUtil.parse(testConstants.expiredPrv);
expect(expiredKey.expiration).to.equal(1567605343000);
expect(expiredKey.usableForEncryptionButExpired).to.equal(true);
expect(expiredKey.missingPrivateKeyForDecryption).to.equal(false);
expect(expiredKey.missingPrivateKeyForSigning).to.equal(false);
t.pass();
});
test(`[unit][Key.usableForEncryptionButExpired] recognizes usable expired key when subkey is expired prior to expired primary key`, async t => {
const expiredKey = await KeyUtil.parse(`-----BEGIN PGP PUBLIC KEY BLOCK-----
xjMEY7FFHhYJKwYBBAHaRw8BAQdAJMyoHhB6M1OIZo592p7H3U2dBphH5JpD
orInOSAmt+3NKjxmbG93Y3J5cHQubm90aWZ5LmV4cGlyaW5nLmtleXNAZ21h
aWwuY29tPsKSBBAWCgBEBQJjsUUeBQkAGl4ABAsJBwgJECM4WSPQXyFDAxUI
CgQWAAIBAhkBAhsDAh4BFiEECp1E5ycyjVgoiNqfIzhZI9BfIUMAAHfrAPwM
bYj191L3f7EbMguhCLLXeyzr2JPTgWtKwCQKjCRpYQEA3MuN0YB/sRwerKvP
CrTy8ZMHj2pc1ezRJJLD9nBqxQrOOARjsUUeEgorBgEEAZdVAQUBAQdArxUe
fRWzL8zjigAdkYHfEevatLcnvx9XOjrLBDwsezIDAQgHwn4EGBYIADAFAmOx
RR4FCQANLwAJECM4WSPQXyFDAhsMFiEECp1E5ycyjVgoiNqfIzhZI9BfIUMA
AAvIAP4+Xu5KG3XNg39ZiS0kJs53JZVY27VeeO1JC3Ns2wqmcAEAnpI4MIP3
QMlzT3kLMkM+vmxB9cgGp3m+CyBttV60vgw=
=RnaH
-----END PGP PUBLIC KEY BLOCK-----`);
expect(expiredKey.usableForEncryptionButExpired).to.equal(true);
expect(expiredKey.expiration).to.equal(1673425950000);
t.pass();
});
test(`[unit][Key.expiration] gives correct expiration when earlier key expires later, in the past, primary key expires too`, async t => {
const expiredKey = await KeyUtil.parse(`-----BEGIN PGP PUBLIC KEY BLOCK-----
xjMEY7P2PBYJKwYBBAHaRw8BAQdAKXZZoF11EaH4wUiCK8O259UZdAzyCJXq
aBWipx5BF3bNKjxmbG93Y3J5cHQubm90aWZ5LmV4cGlyaW5nLmtleXNAZ21h
aWwuY29tPsKSBBAWCgBEBQJjs/Y8BQkAGl4ABAsJBwgJEJtkS3XZon29AxUI
CgQWAAIBAhkBAhsDAh4BFiEESY3/9QK9ExLp4cC6m2RLddmifb0AAIqOAQDl
1D0cOiJwq5SGSKvBSlaWjnZ/jQWLLimQl8y+Z5/jmgEAz1Ma/A14hqNF5RIU
QnIgec4kaPWy2SWQyr84+GbFFAjOOARjs/Y8EgorBgEEAZdVAQUBAQdA1b9l
NzfymR2zXedT6A7SdDGqhdwI66oeDHrZrAo0kTcDAQgHwn4EGBYIADAFAmOz
9jwFCQANLwAJEJtkS3XZon29AhsMFiEESY3/9QK9ExLp4cC6m2RLddmifb0A
AP1OAQCarm1IbTdsVAEOYWT5wTW3TViSatrsNH6bM2BfW4ehnwEAt8cE2rpA
JZjJMVf3WPULEjdBktqxLYCnWX+l76sB7wLOOARjtpk8EgorBgEEAZdVAQUB
AQdAbZeKralww1T0SHDYhJs+Jz13UyqpR8pMSOsGPL3ZKEIDAQgHwn4EGBYI
ADAFAmO2mTwFCQAGl4AJEJtkS3XZon29AhsMFiEESY3/9QK9ExLp4cC6m2RL
ddmifb0AAPebAQDtM5w7GW/lwY6hWVt2KUTn0V5J/67PmpYapK/EkHENDgD/
fXoskfWDbX6oo8PPW+T5OGYJm2Tk7ozgcg2ezmUgEg8=
=JqXW
-----END PGP PUBLIC KEY BLOCK-----`);
expect(expiredKey.usableForEncryptionButExpired).to.equal(true);
expect(expiredKey.expiration).to.equal(1673602364000);
t.pass();
});
test(`[unit][Key.expiration] gives correct expiration when earlier key expires later, in the past, non-expiring primary key`, async t => {
const expiredKey = await KeyUtil.parse(`-----BEGIN PGP PUBLIC KEY BLOCK-----
xjMEY7QObRYJKwYBBAHaRw8BAQdAczbosTYkDbJ6Xd0/kAdMRGzDnpRbF5Th
zhRHEs5+uR7NKjxmbG93Y3J5cHQubm90aWZ5LmV4cGlyaW5nLmtleXNAZ21h
aWwuY29tPsKMBBAWCgA+BQJjtA5tBAsJBwgJEF1Ram9HtamJAxUICgQWAAIB
AhkBAhsDAh4BFiEE2eoEaD/F9813lUXtXVFqb0e1qYkAALurAQDrk80QfXEY
LwXJJrL9bHJIP0kya9cMSvEb8JKzXIzpIAEA3KM2EfDoi/1YS/JTFrEOn1i1
i/7lai45fVRmRB27Fw7OOARjtA5tEgorBgEEAZdVAQUBAQdAVRKAk9ImOIOx
HjE1NjoSm8J4+nxRCgJJwAc1ha4MTTIDAQgHwn4EGBYIADAFAmO0Dm0FCQAN
LwAJEF1Ram9HtamJAhsMFiEE2eoEaD/F9813lUXtXVFqb0e1qYkAABATAP4r
UImxLz0ms9p0uUEemR4MkeJ2Iui7nUO599X4cQCugQEA2KAxUuf4IC7oY5YJ
SuLyf8KT9m8kkXjHrA9PAIhEowHOOARjtrFtEgorBgEEAZdVAQUBAQdAFJ/Q
ZKQpr6ei1cBefr+z8hCEwFyMEzIxVfJY8QUsMH4DAQgHwn4EGBYIADAFAmO2
sW0FCQAGl4AJEF1Ram9HtamJAhsMFiEE2eoEaD/F9813lUXtXVFqb0e1qYkA
AHdQAQDhNHD2qXs3FsbPCGYuqBpI5mFet64slrmgF/qw082jJAD+IV0r8J7s
3iJakM0iGN7IBWTT03Rr4wV/RfVbSzpsrQ8=
=uMzH
-----END PGP PUBLIC KEY BLOCK-----`);
expect(expiredKey.usableForEncryptionButExpired).to.equal(true);
expect(expiredKey.expiration).to.equal(1673608557000);
t.pass();
});
test(`[unit][Key.expiration] gives correct expiration when earlier key expires later, in the future, non-expiring primary key`, async t => {
const expiringKey = await KeyUtil.parse(`-----BEGIN PGP PUBLIC KEY BLOCK-----
xjMEY7QXVRYJKwYBBAHaRw8BAQdAjAQdAmp9L2Xyc/oxarZmXFvKiNBAQb+K
a8tnPI4LXRHNKjxmbG93Y3J5cHQubm90aWZ5LmV4cGlyaW5nLmtleXNAZ21h
aWwuY29tPsKMBBAWCgA+BQJjtBdVBAsJBwgJEM5qLrmJzYEeAxUICgQWAAIB
AhkBAhsDAh4BFiEEHkmnB9FtCPOyqjRbzmouuYnNgR4AAB3+AQCmMhnTFAu+
HZ53xI5VNfcEDH8eJUzVws9Ua1Ob02+mbQD9FbrzBpChCw/r3xxEAfTvEfIa
m7p0GlQeUwcJwC/FCgbOOARjtBdVEgorBgEEAZdVAQUBAQdAjl5oDyPuDsyf
CytKq7Rk7v619xg0MJH4x1yy8OhjDhYDAQgHwn4EGBYIADAFAmO0F1UFCSWl
NQAJEM5qLrmJzYEeAhsMFiEEHkmnB9FtCPOyqjRbzmouuYnNgR4AAEd1AP42
HH8Xfpy66FxdNtgDMHVS23rKdlD+T+OCiO+UixsRAAEAuIcTCfi5ZRnjrH2/
Rk0gHr57uH2Du1DlC2Be6cT7kAfOOARjtrpVEgorBgEEAZdVAQUBAQdAdAZf
8udXJ69BsjaIY8Zh9QH1SKT8z85AdlzvkMA1ImYDAQgHwn4EGBYIADAFAmO2
ulUFCSWenYAJEM5qLrmJzYEeAhsMFiEEHkmnB9FtCPOyqjRbzmouuYnNgR4A
ACu7AP904FUsOvvYhiJJ2GIWwxqnWuhqtz0rKY1Xoxk0vhBmzQD+NqVK9O1/
qC2PFoU1J4aEVe5Jz2yovJnzkx/aa0Hs4g0=
=ZB8L
-----END PGP PUBLIC KEY BLOCK-----`);
expect(expiringKey.usableForSigning).to.equal(true);
expect(expiringKey.usableForEncryption).to.equal(true);
expect(expiringKey.expiration).to.equal(2304330837000);
t.pass();
});
test('[unit][PgpPwd.random] produces string of correct pattern', async t => {
const generatedString = PgpPwd.random();
// eg TDW6-DU5M-TANI-LJXY
expect(/^[A-Z0-9]{4}-[A-Z0-9]{4}-[A-Z0-9]{4}-[A-Z0-9]{4}$/.test(generatedString)).to.equal(true);
t.pass();
});
test('[unit][Str.is7bit] correctly detects presence of non-7bit characters', async t => {
const UNICODE = `abcგ`;
const UNICODE_AS_BYTES = Buf.fromUtfStr(UNICODE);
const ASCII = 'Simple ASCII text\r\nwith line breaks';
const ASCII_AS_BYTES = Buf.fromUtfStr(ASCII);
expect(Str.is7bit(UNICODE)).to.be.false;
expect(Str.is7bit(UNICODE_AS_BYTES)).to.be.false;
expect(Str.is7bit(ASCII)).to.be.true;
expect(Str.is7bit(ASCII_AS_BYTES)).to.be.true;
t.pass();
});
test('[unit][KeyUtil.parse] S/MIME key parsing works', async t => {
/*
// generate a key pair
const keys = forge.pki.rsa.generateKeyPair(2048);
// create a certification request (CSR)
const csr = forge.pki.createCertificationRequest();
csr.publicKey = keys.publicKey;
csr.setSubject([{
name: 'commonName',
value: 'smime@recipient.com'
}]);
csr.sign(keys.privateKey);
// issue a certificate based on the csr
const cert = forge.pki.createCertificate();
cert.serialNumber = '20211103'; // todo: set something unique here
cert.validity.notBefore = new Date();
cert.validity.notAfter = new Date();
cert.validity.notAfter.setFullYear(cert.validity.notBefore.getFullYear() + 100);
cert.setSubject(csr.subject.attributes);
const caCertPem = readFileSync("./ca.crt", 'utf8');
const caKeyPem = readFileSync("./ca.key", 'utf8');
const caCert = forge.pki.certificateFromPem(caCertPem);
const caKey = forge.pki.decryptRsaPrivateKey(caKeyPem, '1234');
cert.setIssuer(caCert.subject.attributes);
cert.setExtensions([{
name: 'basicConstraints',
cA: true
}, {
name: 'keyUsage',
keyCertSign: true,
digitalSignature: true,
nonRepudiation: true,
keyEncipherment: true,
dataEncipherment: true
}, {
name: 'extKeyUsage',
emailProtection: true
}
]);
cert.publicKey = csr.publicKey;
cert.sign(caKey);
const pem = forge.pki.certificateToPem(cert);
console.log(pem);
const p12asn1 = forge.pkcs12.toPkcs12Asn1(keys.privateKey, cert, 'try_me');
const rawString = forge.asn1.toDer(p12asn1).getBytes();
let buf = Buf.fromRawBytesStr(pem);
writeFileSync("./smime.crt", buf);
buf = Buf.fromRawBytesStr(rawString);
writeFileSync("./test.p12", buf); */
const key = await KeyUtil.parse(testConstants.smimeCert);
expect(key.id).to.equal('1D695D97A7C8A473E36C6E1D8C150831E4061A74');
expect(key.family).to.equal('x509');
expect(key.usableForEncryption).to.equal(true);
expect(key.usableForSigning).to.equal(true);
expect(key.usableForEncryptionButExpired).to.equal(false);
expect(key.usableForSigningButExpired).to.equal(false);
expect(key.users.length).to.equal(1);
expect(key.users[0].email).to.equal('smime@recipient.com');
expect(key.users.length).to.equal(1);
expect(key.users[0].full).to.equal('smime@recipient.com');
expect(key.isPublic).to.equal(true);
expect(key.isPrivate).to.equal(false);
expect(key.expiration).to.not.equal(undefined);
t.pass();
});
const httpsCert = `-----BEGIN CERTIFICATE-----
MIIGqzCCBZOgAwIBAgIQB0/pAsa31hmIThyhhU2ReDANBgkqhkiG9w0BAQsFADBN
MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMScwJQYDVQQDEx5E
aWdpQ2VydCBTSEEyIFNlY3VyZSBTZXJ2ZXIgQ0EwHhcNMTkwNzA4MDAwMDAwWhcN
MjEwOTEwMTIwMDAwWjB2MQswCQYDVQQGEwJVUzETMBEGA1UECBMKQ2FsaWZvcm5p
YTEWMBQGA1UEBxMNU2FuIEZyYW5jaXNjbzEbMBkGA1UEChMSWSBDb21iaW5hdG9y
LCBJbmMuMR0wGwYDVQQDExRuZXdzLnljb21iaW5hdG9yLmNvbTCCASIwDQYJKoZI
hvcNAQEBBQADggEPADCCAQoCggEBAMsNA6BafLAJyN3SjorK4fq6P8oArZLHCHwB
uf4NQ0Oo/CdMgrV28/PM4yh2U0++zL9ZuS3foqMOSwy6DZbZIfBa/WBjhJKd4/gy
2yJwOGwSsIyVMpQ/HsBrZRruN2oEiu4inE4hPyYC03Z7zRlTDOuxDDBOJjuKMYRr
aMlzOqj7ZZDLAOYgRDoGHTGF1AnqT+ZsV98rXCijgFGvHTaXqJxcz+edKfHTzy+n
jsgbbbBJ9jGATX8qXqdqjCHm6D5G6hJ2MfcQt4Ohd5sm8BKvZAEMCcsLww2ijwx9
j7ZadN7n7dOp5sY32BEhe7l0ki22TDS+pcaySoP8E5axqrnAMkUCAwEAAaOCA1ww
ggNYMB8GA1UdIwQYMBaAFA+AYRyCMWHVLyjnjUY4tCzhxtniMB0GA1UdDgQWBBQO
JfQVakUgYp9x0ncgzQTXXFjfOjAfBgNVHREEGDAWghRuZXdzLnljb21iaW5hdG9y
LmNvbTAOBgNVHQ8BAf8EBAMCBaAwHQYDVR0lBBYwFAYIKwYBBQUHAwEGCCsGAQUF
BwMCMGsGA1UdHwRkMGIwL6AtoCuGKWh0dHA6Ly9jcmwzLmRpZ2ljZXJ0LmNvbS9z
c2NhLXNoYTItZzYuY3JsMC+gLaArhilodHRwOi8vY3JsNC5kaWdpY2VydC5jb20v
c3NjYS1zaGEyLWc2LmNybDBMBgNVHSAERTBDMDcGCWCGSAGG/WwBATAqMCgGCCsG
AQUFBwIBFhxodHRwczovL3d3dy5kaWdpY2VydC5jb20vQ1BTMAgGBmeBDAECAjB8
BggrBgEFBQcBAQRwMG4wJAYIKwYBBQUHMAGGGGh0dHA6Ly9vY3NwLmRpZ2ljZXJ0
LmNvbTBGBggrBgEFBQcwAoY6aHR0cDovL2NhY2VydHMuZGlnaWNlcnQuY29tL0Rp
Z2lDZXJ0U0hBMlNlY3VyZVNlcnZlckNBLmNydDAMBgNVHRMBAf8EAjAAMIIBfQYK
KwYBBAHWeQIEAgSCAW0EggFpAWcAdgDuS723dc5guuFCaR+r4Z5mow9+X7By2IMA
xHuJeqj9ywAAAWvSsgGGAAAEAwBHMEUCIQDuwilh2VuUnkTH0tmDUbAdKWDxFukD
m/4EktTbiwgFNAIgZltmbZUzknxDpGUXkVLpFmWTogu4wAGxh72hbbFp804AdgCH
db/nWXz4jEOZX73zbv9WjUdWNv9KtWDBtOr/XqCDDwAAAWvSsgIeAAAEAwBHMEUC
IQDzAY1oWZD1mhX+nCKORP4DxtO3AnhLSUMOyvv3OBbICQIgWWzTJP2gsPM6vHux
kb6fQtPekabXk0nhrOScMHr/cvAAdQBElGUusO7Or8RAB9io/ijA2uaCvtjLMbU/
0zOWtbaBqAAAAWvSsgEiAAAEAwBGMEQCIFqbAfpfnJFvd4miwlb3ZMCy/tph+qn6
0gFBIGhOFVlQAiBqo/dlgJEfPJU2pjPlR22kl7wTbnFnbVabTAy8eKx+DjANBgkq
hkiG9w0BAQsFAAOCAQEARcovgnGiFSc6ve8yTxFOho47wBKXwYAUfoGiiRFybcX6
43JcEMyH6KYU8qnfhKzp9juYBXTuc+4BqLP8fGdrP6I7xfYux6PWdhZ9ReVxZhrn
+7neAPnr4IcDyUMGB3bqn4wslL8Go1+dHKfM+Ix8k/+ytaXWYZQgiWNwmuR3Piay
vo5ioURVp9Hm28b1A5o828aXph6nbPhyaLD5gUdQTuprQGpJMo2tL9AmZhtw3iPH
Nu6RzBFp27492OM1t0vvbEsNkMgD3/wSCMev5rleor1bvTT+GkSEArEdpHRydtcN
WeNYP84Yjw6OFSHdi2W0VojRGhxm7PZCMqswN/XaBg==
-----END CERTIFICATE-----`;
test('[unit][KeyUtil.parse] S/MIME key parsing of HTTPS cert', async t => {
// parsing throws because the domain name doesn't look like an e-mail
// address
await t.throwsAsync(() => KeyUtil.parse(httpsCert), {
instanceOf: UnreportableError,
message: 'This S/MIME x.509 certificate has an invalid recipient email: news.ycombinator.com',
});
});
test('[unit][KeyUtil.parse] Unknown key family parsing fails', async t => {
await t.throwsAsync(() => KeyUtil.parse('dummy string for unknown key'), {
instanceOf: Error,
message: 'Key type is unknown, expecting OpenPGP or x509 S/MIME',
});
});
const expiredPgp = `-----BEGIN PGP PRIVATE KEY BLOCK-----
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
=wYbc
-----END PGP PRIVATE KEY BLOCK-----`;
test('[unit][KeyUtil.parse] OpenPGP parsing of expired key', async t => {
const key = await KeyUtil.parse(expiredPgp);
expect(key.id).to.equal('3449178FCAAF758E24CB68BE62CB4E6F9ECA6FA1');
expect(key.allIds.length).to.equal(2);
expect(key.allIds[0]).to.equal('3449178FCAAF758E24CB68BE62CB4E6F9ECA6FA1');
expect(key.allIds[1]).to.equal('2D3391762FAC9394F7D5E9EDB30FE36B3AEC2F8F');
expect(key.family).to.equal('openpgp');
expect(key.usableForEncryption).equal(false);
expect(key.usableForSigning).equal(false);
expect(key.usableForEncryptionButExpired).equal(true);
expect(key.missingPrivateKeyForDecryption).to.equal(false);
expect(key.missingPrivateKeyForSigning).to.equal(false);
expect(key.users.length).to.equal(1);
expect(key.users[0].email).to.equal('flowcrypt@metacode.biz');
expect(key.users.length).to.equal(1);
expect(key.users[0].full).to.equal('Testing <flowcrypt@metacode.biz>');
expect(key.isPublic).equal(false);
expect(key.isPrivate).equal(true);
expect(key.expiration).to.equal(63074017000);
t.pass();
});
const notExpiredPgp = `-----BEGIN PGP PRIVATE KEY BLOCK-----
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
cmKFmmDYm+rrWuAv6Q==
=oWPu
-----END PGP PRIVATE KEY BLOCK-----`;
test('[unit][KeyUtil.parse] OpenPGP parsing of not-expired key', async t => {
const key = await KeyUtil.parse(notExpiredPgp);
expect(key.id).to.equal('7C3B38BB2C8A7E693C29DF455C08033166AF91E3');
expect(key.allIds.length).to.equal(2);
expect(key.allIds[0]).to.equal('7C3B38BB2C8A7E693C29DF455C08033166AF91E3');
expect(key.allIds[1]).to.equal('28A4CCBFA1AF056C3B73EA4DECF8F9D42D8DFED8');
expect(key.family).to.equal('openpgp');
expect(key.usableForEncryption).equal(true);
expect(key.usableForSigning).equal(true);
expect(key.usableForEncryptionButExpired).equal(false);
expect(key.usableForSigningButExpired).equal(false);
expect(key.missingPrivateKeyForDecryption).to.equal(false);
expect(key.missingPrivateKeyForSigning).to.equal(false);
expect(key.users.length).to.equal(1);
expect(key.users[0].email).to.equal('expiration_100years@test.com');
expect(key.users.length).to.equal(1);
expect(key.users[0].full).to.equal('Testing <expiration_100years@test.com>');
expect(key.isPublic).equal(false);
expect(key.isPrivate).equal(true);
expect(key.expiration).to.equal(4773398996000);
t.pass();
});
const nonExpiringKey = `-----BEGIN PGP PUBLIC KEY BLOCK-----
mDMEXylIoBYJKwYBBAHaRw8BAQdAMgtGtZnSa/oq2FHZ7Ow7rnCpRDJ5+WlojNXt
6r74RdW0DE5vdCBFeHBpcmluZ4iQBBMWCAA4FiEEjdc3wIbaszE+t21fSpIVLfL9
bb0FAl8pSKACGwMFCwkIBwIGFQoJCAsCBBYCAwECHgECF4AACgkQSpIVLfL9bb1J
/QD8CDQrGNP3ZvSQUoA7kTQURLO9qkctY6Yn1+GsJR6M3zQA/ievQawWchCZVzgT
SBC9rHNi6GbYSn3Tm+PnsUOe9g8NuDgEXylIoBIKKwYBBAGXVQEFAQEHQLU+tO+s
CpD7n1C0Mg9Yzghr9pMps9UaexwMuxxVeWZcAwEIB4h4BBgWCAAgFiEEjdc3wIba
szE+t21fSpIVLfL9bb0FAl8pSKACGwwACgkQSpIVLfL9bb39XgD6A91LwqK+CEzl
McqZHXuttXHc2wZ2nvjjtbzWSEzxvpAA/jdWwCNBg65Wh93Df5/6Ec05W8AgFwJH
/NBHfzBA90AM
=E46c
-----END PGP PUBLIC KEY BLOCK-----`;
test('[unit][KeyUtil.parse] OpenPGP parsing of never expiring key', async t => {
const key = await KeyUtil.parse(nonExpiringKey);
expect(key.id).to.equal('8DD737C086DAB3313EB76D5F4A92152DF2FD6DBD');
expect(key.expiration).to.equal(undefined);
t.pass();
});
const pgpArmoredTwoKeys = `-----BEGIN PGP PUBLIC KEY BLOCK-----
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
zZFGf6poIjKUC8V2Zww6
=Hjpw
-----END PGP PUBLIC KEY BLOCK-----`;
test('[unit][KeyUtil.readMany] Parsing two OpenPGP armored together keys', async t => {
const { keys, errs } = await KeyUtil.readMany(Buf.fromUtfStr(pgpArmoredTwoKeys));
expect(keys.length).to.equal(2);
expect(errs.length).to.equal(0);
expect(keys.some(key => key.id === '5A5F75AEA28751C3EE8CFFC3AC5F0CE1BB2B99DD')).to.equal(true);
expect(keys.some(key => key.id === 'BBC75684E46EF0948D31359992C4E7841B3AFF74')).to.equal(true);
expect(keys.every(key => key.family === 'openpgp')).to.equal(true);
t.pass();
});
const pgpArmoredSeparate = `-----BEGIN PGP PUBLIC KEY BLOCK-----
mQENBFzYItQBCAD1FmP71FRRC3nkaIltLH2oZklMY+SAbnses8o5e3wj3DKQk3P4
ZTxALAl9g5GtYqYE5vA2SOSRiuiVD+ibYDOAAEjBBKYlfaFy1UCFAK5gibhP1o04
eEuRuAJt9JJO0aFTj+cwWx/wII+ledavdiWHg1T80JEoHV+EMlvNB51ydZGnSjPs
ntQTO4tQzp1knA9hS20O6g6gLakaNlEc1m3G445d8tWj7V3plLR+FegYWoCJkQ4F
3W6jLSM4ErfLQpj6Ew6bk65wRC8XSNFJAImfVGd40tAtYcHFf/HE3D3NLH3OhFc3
EUnaQtCEqrWqBD/HFdGvqmbVYL3KWusJCXafABEBAAG0IFRlc3QgV0tEIDx0ZXN0
LXdrZEBtZXRhY29kZS5iaXo+iQFUBBMBCAA+FiEEWl91rqKHUcPujP/DrF8M4bsr
md0FAlzYItQCGwMFCQPCZwAFCwkIBwIGFQoJCAsCBBYCAwECHgECF4AACgkQrF8M
4bsrmd3Ftwf+OhNoF3bq1o7QFJVt6bfIEN1Uqf2+h11pO60ZGJNjLm45rdy2Jt4r
Q86h/XUONGDfV28Pj/Gwz7/ruB4YcFL83HwuGHHh/QZsbHvmopycSS7DQp6yiiSk
M0sLBfbG5jxQpgahrsVqPDhWhvuNCGrWFqEPAmLWyz7CyvtVbglL4cB9V7W9eHFz
FsLSITLR9LJybqqIIRXerpc/cj4ZI0ll/nZbMuFsY/CWbfdxk6CsFTAQ7gegXs/c
QaKQuQJFtl46CFCjUMJJN3SSsaihOu5V3zVWJdW1GJXQWpe2Rir2MqzhSuW0Uw8g
/nd2sNwmriQRfQd7d46YOLcBqVksGiWCb7kBDQRc2CLUAQgA10E6Da2YSXL/6VKJ
YaFNgBi9p7pko6KzrSD8AGxjpDo7BV11jBNImZxP3WrmjPfFYuIET+Lt05St/fdn
tkiHkkPimKNSmH46E0lRnULO3Tmo7Eiu/CMPI5Oj9KZr8fQBvFOh2qFr+dgflIHr
WGO94tIeRq2jdZblGEtOY8kfqf7/WbzaVhIJM/zaYyeQFLv+CNkc2AMIDpsBmKPL
7wrQzGi1JK4Xn7JdRgMoEVFJJFsDkByvp6mvwjRgXkG/UKnDf75vOJzCP1i/6ESb
fu0+pz/gsiwPIcEnS+1F2LWmV2EQTf4FPz75J31bGixP2qIa+Macr+AepreugpFq
gdUQjwARAQABiQE8BBgBCAAmFiEEWl91rqKHUcPujP/DrF8M4bsrmd0FAlzYItQC
GwwFCQPCZwAACgkQrF8M4bsrmd3GZQf+OLkOd0ddebGvLgcQrUANuKASTbke+PUD
laTTpdJlhufLqXHAL3ydZjnmFXEOYQPzWr1DgaUEiEtiw4MO3lWKXj3+J200jPpg
+XEFNXneXrZEJoG/q4h2hB0icP/1k1q+FsSgc3I6kVsGBmnMasJ+j9FhUJ/JECJS
gQwP7QeE0O0oa8zQU+INc6viBkV1F3FP5PvIfVjfmw8yqlhZgeCSSavo/kEgyTlg
cItxVc8FURPk2BkmWYjZ+N0uNOYrI7FGOHdoW4j9rmuOhtYHGLRC0mAcADIRcv7p
ZlBF2eTk0HMhizTvGmOWcv5htkCqxpCSNnjHLsM6YCl+CcW6a8U7/g==
=46pe
-----END PGP PUBLIC KEY BLOCK-----
-----BEGIN PGP PUBLIC KEY BLOCK-----
mQENBF4pefQBCADjcgq0Wi7ZCDiTCnMnG5PTnTU5nWW7eg9ofEjV9SbDDiHV2SkJ
ZBynFzGgAaWs/x3FINwNffnE/Yc4iokd1wk3tlhcfvzLUMZW9ZrTPAGOioSKa621
nQjTKkln2kuU60oeG5CJocIiLroLHay9N9kVvlidH4dVbC9+jyvdcjieq3RUnXVC
zpF3vZN/aTgbjecyfQY4oeiMglpBImTdPw/7k3Xz6L7D2o8wIuVyS7KxTrIsmR1F
uTkfAWzfhI4rZHxyzWmCWZnC+N+rLVdLOo4V7op6SZE1x3Hhy2BXChBzjIZbJ1/J
eIQLMk8WaszkGc0uXFXsF/fB+g9woa7cRjT5ABEBAAG0FXRlc3Qtd2tkQG1ldGFj
b2RlLmJpeokBVAQTAQgAPhYhBLvHVoTkbvCUjTE1mZLE54QbOv90BQJeKXn0AhsD
BQkDwmcABQsJCAcCBhUKCQgLAgQWAgMBAh4BAheAAAoJEJLE54QbOv908+YH+gM4
kpkNL7IWUjRJqmuSybWT23BKtFq4/P1HhGyUKmIq/ZqjBIzR+rgmfM5qw/HYWO+l
FGPoCeusooMauWWHZ5rbNrP05cwRlRvCg+0GXJ3Yw2tzOayXq5SL3QLa//P2z4yt
Wa9pl5EXIzAWNKlGiuTmZ19tDIrNg3tJQKhdd9MNRite4ZyMvkMB9l/d8PSIIa9J
bbOaQZa0WH6lg417DHAhWIzcI8PLW9hXoeGkOy5z7ah9H7+rZJAcz/Z2Qfo0zu4+
vQnzefA9vzd+d9CRr3ExZMNxpSykH4l4PbYjMpKfLRpdsLvvLnV2H06KZEcxBsn0
czaln5Xm/0wga57IgFW5AQ0EXil59AEIAMW00JciIzastXINdy0Lx15ikpkwQe/o
BO0zxifSRwsvpgh3KdvKV2DfrEagvVXpjv3GV0li5bvdn9mKwDkpVttHddV4u4Sy
ATEN733x9EjhcZf8CQRbqDnJKjpDLI2xpUf9R5coI1je9agNeyOsppXnVs371O4L
aVYzdXLUtZ6LqMN/F0xVbDEEZsFzBI+0Z0f1b0tHuExF3xvBg6bodS7Tnel3LP0x
ZGb1EiwNMo+9GbLkpr64YkUrXvY/L7jm0eQSYmxUTD5o+EDKhizxDCD7adqCzhhK
XtpDsXH1M1N15SzJlchzbXBVY+cNC+6OCmsKFjbhGtFaEFSE/kdBMZMAEQEAAYkB
NgQYAQgAIBYhBLvHVoTkbvCUjTE1mZLE54QbOv90BQJeKXn0AhsMAAoJEJLE54Qb
Ov900VcH/RGkjO1VfyKPgm17PttvC6PtvSAERvEhSRTy3i2ds4s3DXtG3Y1N6NU/
YqolPFrY6PtFX0tTSeN4Zw1x9WckD0hI6qWsj0gcdwK05MmUn8TW0gKK2DwghLZt
3vtn25PuQtzIz/ahwMkOYM5vfJLH3ZoWt7yF/zrvuul9t4bM6ZaNA8uGJf+bYObx
CnSPBUoy8XAfrMg9YDF8djFjhGktbTMWY3feKbzPUHBfT6kii+D+Hmj/C2eb8gYx
XPk9f8zmQi8NqUMzsOQokj9iq+UB1cC3korAsuCVmC3El6cJbQvWEAMEVOFdY3pA
vpQiyk4ceuTNkUZ/qmgiMpQLxXZnDDo=
=lQQh
-----END PGP PUBLIC KEY BLOCK-----`;
test('[unit][KeyUtil.readMany] Parsing two OpenPGP armored separate keys', async t => {
const { keys, errs } = await KeyUtil.readMany(Buf.fromUtfStr(pgpArmoredSeparate));
expect(keys.length).to.equal(2);
expect(errs.length).to.equal(0);
expect(keys.some(key => key.id === '5A5F75AEA28751C3EE8CFFC3AC5F0CE1BB2B99DD')).to.equal(true);
expect(keys.some(key => key.id === 'BBC75684E46EF0948D31359992C4E7841B3AFF74')).to.equal(true);
expect(keys.every(key => key.family === 'openpgp')).to.equal(true);
t.pass();
});
test('[unit][KeyUtil.readMany] Parsing one S/MIME key', async t => {
const { keys, errs } = await KeyUtil.readMany(Buf.fromUtfStr(testConstants.smimeCert));
expect(keys.length).to.equal(1);
expect(errs.length).to.equal(0);
expect(keys[0].id).to.equal('1D695D97A7C8A473E36C6E1D8C150831E4061A74');
expect(keys[0].family).to.equal('x509');
t.pass();
});
test('[unit][KeyUtil.parse] S/MIME key parsing of unprotected PKCS#8 private key and mismatching certificate', async t => {
await t.throwsAsync(
() =>
KeyUtil.parse(`${testConstants.smimeUnencryptedKey}
${testConstants.smimeCert}`),
{ instanceOf: UnreportableError, message: `Certificate doesn't match the private key` }
);
t.pass();
});
test('[unit][KeyUtil.decrypt] S/MIME key decryption of mismatching private key', async t => {
const encryptedKey = await KeyUtil.parse(`${testConstants.smimeEncryptedKey}
${testConstants.smimeCert}`);
await t.throwsAsync(() => KeyUtil.decrypt(encryptedKey, 'AHbxhwquX5pc'), {
instanceOf: UnreportableError,
message: `Certificate doesn't match the private key`,
});
t.pass();
});
test(`[unit][KeyUtil.decrypt] throws on incorrect PKCS#8 encrypted private key`, async t => {
const encryptedKey = await KeyUtil.parse(`-----BEGIN ENCRYPTED PRIVATE KEY-----
AAAAAAAAAAAAAAAAzzzzzzzzzzzzzzzzzzzzzzzzzzzz.....
-----END ENCRYPTED PRIVATE KEY-----
${testConstants.smimeCert}`);
await t.throwsAsync(() => KeyUtil.decrypt(encryptedKey, '123'), {
instanceOf: Error,
message: `Invalid PEM formatted message.`,
});
t.pass();
});
test(`[unit][KeyUtil.parse] throws on incorrect PKCS#8 private key`, async t => {
await t.throwsAsync(
() =>
KeyUtil.parse(`-----BEGIN PRIVATE KEY-----
AAAAAAAAAAAAAAAAzzzzzzzzzzzzzzzzzzzzzzzzzzzz.....
-----END PRIVATE KEY-----
${testConstants.smimeCert}`),
{ instanceOf: Error, message: `Invalid PEM formatted message.` }
);
t.pass();
});
test(`[unit][KeyUtil.parse] throws on incorrect RSA PKCS#8 private key`, async t => {
await t.throwsAsync(
() =>
KeyUtil.parse(`-----BEGIN RSA PRIVATE KEY-----
AAAAAAAAAAAAAAAAzzzzzzzzzzzzzzzzzzzzzzzzzzzz.....
-----END RSA PRIVATE KEY-----
${testConstants.smimeCert}`),
{ instanceOf: Error, message: `Invalid PEM formatted message.` }
);
t.pass();
});
test('[unit][KeyUtil.armor] S/MIME key from PKCS#12 is armored to PKCS#8', async t => {
const p12 = readFileSync('test/samples/smime/human-pwd-original-PKCS12.pfx', 'binary');
const key = SmimeKey.parseDecryptBinary(Buf.fromRawBytesStr(p12), 'AHbxhwquX5pc');
expect(key.id).to.equal('9B5FCFF576A032495AFE77805354351B39AB3BC6');
expect(key.fullyDecrypted).to.equal(true);
const armoredDecrypted = KeyUtil.armor(key);
expect(armoredDecrypted).to.not.include('-----BEGIN ENCRYPTED PRIVATE KEY-----');
expect(armoredDecrypted).to.include('-----END RSA PRIVATE KEY-----\r\n-----BEGIN CERTIFICATE-----');
await KeyUtil.encrypt(key, 're-encrypt');
expect(key.fullyDecrypted).to.equal(false);
const armoredEncrypted = KeyUtil.armor(key);
expect(armoredEncrypted).to.not.include('-----BEGIN RSA PRIVATE KEY-----');
expect(armoredEncrypted).to.include('-----END ENCRYPTED PRIVATE KEY-----\r\n-----BEGIN CERTIFICATE-----');
const parsedDecrypted = await KeyUtil.parse(armoredDecrypted);
expect(parsedDecrypted.id).to.equal('9B5FCFF576A032495AFE77805354351B39AB3BC6');
expect(parsedDecrypted.fullyDecrypted).to.equal(true);
const parsedEncrypted = await KeyUtil.parse(armoredEncrypted);
expect(parsedEncrypted.id).to.equal('9B5FCFF576A032495AFE77805354351B39AB3BC6');
expect(parsedEncrypted.fullyDecrypted).to.equal(false);
await KeyUtil.decrypt(parsedEncrypted, 're-encrypt');
expect(parsedEncrypted.fullyDecrypted).to.equal(true);
t.pass();
});
test('[unit][KeyUtil.readMany] Parsing unarmored S/MIME certificate', async t => {
const pem = forge.pem.decode(testConstants.smimeCert)[0];
const { keys, errs } = await KeyUtil.readMany(Buf.fromRawBytesStr(pem.body));
expect(keys.length).to.equal(1);
expect(errs.length).to.equal(0);
expect(keys[0].id).to.equal('1D695D97A7C8A473E36C6E1D8C150831E4061A74');
expect(keys[0].family).to.equal('x509');
t.pass();
});
test('[unit][KeyUtil.parse] issuerAndSerialNumber of S/MIME certificate is constructed according to PKCS#7', async t => {
const key = await KeyUtil.parse(testConstants.smimeCert);
const buf = Buf.with((await MsgUtil.encryptMessage({ pubkeys: [key], data: Buf.fromUtfStr('anything'), armor: false })).data);
const raw = buf.toRawBytesStr();
expect(raw).to.include(key.issuerAndSerialNumber);
t.pass();
});
test('[unit][MsgUtil.encryptMessage] duplicate S/MIME recipients are collapsed into one', async t => {
const key = await KeyUtil.parse(testConstants.smimeCert);
const buf = Buf.with((await MsgUtil.encryptMessage({ pubkeys: [key, key, key], data: Buf.fromUtfStr('anything'), armor: false })).data);
const msg = buf.toRawBytesStr();
const p7 = forge.pkcs7.messageFromAsn1(forge.asn1.fromDer(msg));
expect(p7.type).to.equal(ENVELOPED_DATA_OID);
if (p7.type === ENVELOPED_DATA_OID) {
expect(p7.recipients.length).to.equal(1);
}
t.pass();
});
test('[unit][MsgUtil.isPasswordMesageEnabled] test password protected message compliance', async t => {
const disallowTerms = ['[Classification: Data Control: Internal Data Control]', 'droid', 'forbidden data'];
const subjectsToTestObj: { [key: string]: boolean } = {
'[Classification: Data Control: Internal Data Control] Quarter results': false,
'Conference information [Classification: Data Control: Internal Data Control]': false,
'Classification: Data Control: Internal Data Control - Tomorrow meeting': true,
'Internal Data Control - Finance monitoring': true,
// term check should work only for exact matches - if we have droid in the list of strings,
// password-protected messages shouldn't be disabled for subjects with Android word
'Android phone update': true,
'droid phone': false,
// Check for case insensitive
'DROiD phone': false,
'[forbidden data] year results': false,
};
for (const subject of Object.keys(subjectsToTestObj)) {
const expectedValue = subjectsToTestObj[subject];
const result = MsgUtil.isPasswordMessageEnabled(subject, disallowTerms);
expect(expectedValue).to.equal(result);
}
t.pass();
});
test('[unit][KeyUtil.parse] Correctly extracting email from SubjectAltName of S/MIME certificate', async t => {
/*
// generate a key pair
const keys = forge.pki.rsa.generateKeyPair(2048);
// create a certification request (CSR)
const csr = forge.pki.createCertificationRequest();
csr.publicKey = keys.publicKey;
csr.setSubject([{
name: 'commonName',
value: 'Jack Doe'
}]);
// set (optional) attributes
const subjectAltName = {
name: 'subjectAltName',
altNames: [{
// 1 is RFC822Name type
type: 1,
value: 'email@embedded.in.subj.alt.name'
}]
}
const extensions = [subjectAltName];
(csr as any).setAttributes([{
name: 'extensionRequest',
extensions
}]);
csr.sign(keys.privateKey);
// issue a certificate based on the csr
const cert = forge.pki.createCertificate();
cert.serialNumber = '1';
cert.validity.notBefore = new Date();
cert.validity.notAfter = new Date();
cert.validity.notAfter.setFullYear(cert.validity.notBefore.getFullYear() + 30);
cert.setSubject(csr.subject.attributes);
const caCertPem = fs.readFileSync("./ca.crt", 'utf8');