api-safe-storage-spec.ts 3.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103
  1. import * as cp from 'child_process';
  2. import * as path from 'path';
  3. import { safeStorage } from 'electron/main';
  4. import { expect } from 'chai';
  5. import { emittedOnce } from './events-helpers';
  6. import { ifdescribe } from './spec-helpers';
  7. import * as fs from 'fs';
  8. /* isEncryptionAvailable returns false in Linux when running CI due to a mocked dbus. This stops
  9. * Chrome from reaching the system's keyring or libsecret. When running the tests with config.store
  10. * set to basic-text, a nullptr is returned from chromium, defaulting the available encryption to false.
  11. *
  12. * Because all encryption methods are gated by isEncryptionAvailable, the methods will never return the correct values
  13. * when run on CI and linux.
  14. */
  15. ifdescribe(process.platform !== 'linux')('safeStorage module', () => {
  16. after(async () => {
  17. const pathToEncryptedString = path.resolve(__dirname, 'fixtures', 'api', 'safe-storage', 'encrypted.txt');
  18. if (fs.existsSync(pathToEncryptedString)) {
  19. await fs.unlinkSync(pathToEncryptedString);
  20. }
  21. });
  22. describe('SafeStorage.isEncryptionAvailable()', () => {
  23. it('should return true when encryption key is available (macOS, Windows)', () => {
  24. expect(safeStorage.isEncryptionAvailable()).to.equal(true);
  25. });
  26. });
  27. describe('SafeStorage.encryptString()', () => {
  28. it('valid input should correctly encrypt string', () => {
  29. const plaintext = 'plaintext';
  30. const encrypted = safeStorage.encryptString(plaintext);
  31. expect(Buffer.isBuffer(encrypted)).to.equal(true);
  32. });
  33. it('UTF-16 characters can be encrypted', () => {
  34. const plaintext = '€ - utf symbol';
  35. const encrypted = safeStorage.encryptString(plaintext);
  36. expect(Buffer.isBuffer(encrypted)).to.equal(true);
  37. });
  38. });
  39. describe('SafeStorage.decryptString()', () => {
  40. it('valid input should correctly decrypt string', () => {
  41. const encrypted = safeStorage.encryptString('plaintext');
  42. expect(safeStorage.decryptString(encrypted)).to.equal('plaintext');
  43. });
  44. it('UTF-16 characters can be decrypted', () => {
  45. const plaintext = '€ - utf symbol';
  46. const encrypted = safeStorage.encryptString(plaintext);
  47. expect(safeStorage.decryptString(encrypted)).to.equal(plaintext);
  48. });
  49. it('unencrypted input should throw', () => {
  50. const plaintextBuffer = Buffer.from('I am unencoded!', 'utf-8');
  51. expect(() => {
  52. safeStorage.decryptString(plaintextBuffer);
  53. }).to.throw(Error);
  54. });
  55. it('non-buffer input should throw', () => {
  56. const notABuffer = {} as any;
  57. expect(() => {
  58. safeStorage.decryptString(notABuffer);
  59. }).to.throw(Error);
  60. });
  61. });
  62. describe('safeStorage persists encryption key across app relaunch', () => {
  63. it('can decrypt after closing and reopening app', async () => {
  64. const fixturesPath = path.resolve(__dirname, 'fixtures');
  65. const encryptAppPath = path.join(fixturesPath, 'api', 'safe-storage', 'encrypt-app');
  66. const encryptAppProcess = cp.spawn(process.execPath, [encryptAppPath]);
  67. let stdout: string = '';
  68. encryptAppProcess.stderr.on('data', data => { stdout += data; });
  69. encryptAppProcess.stderr.on('data', data => { stdout += data; });
  70. try {
  71. await emittedOnce(encryptAppProcess, 'exit');
  72. const appPath = path.join(fixturesPath, 'api', 'safe-storage', 'decrypt-app');
  73. const relaunchedAppProcess = cp.spawn(process.execPath, [appPath]);
  74. let output = '';
  75. relaunchedAppProcess.stdout.on('data', data => { output += data; });
  76. relaunchedAppProcess.stderr.on('data', data => { output += data; });
  77. const [code] = await emittedOnce(relaunchedAppProcess, 'exit');
  78. if (!output.includes('plaintext')) {
  79. console.log(code, output);
  80. }
  81. expect(output).to.include('plaintext');
  82. } catch (e) {
  83. console.log(stdout);
  84. throw e;
  85. }
  86. });
  87. });
  88. });