| 
 | 1 | +// Copyright (c) Microsoft Corporation.  | 
 | 2 | +// Licensed under the MIT license.  | 
 | 3 | + | 
 | 4 | +import { assert } from "chai";  | 
 | 5 | +import "mocha";  | 
 | 6 | +import { createSandbox } from "sinon";  | 
 | 7 | +import Cryptr from "cryptr";  | 
 | 8 | +import { LocalCrypto } from "../../src/core/crypto";  | 
 | 9 | +import { SystemError } from "@microsoft/teamsfx-api";  | 
 | 10 | + | 
 | 11 | +describe("LocalCrypto", () => {  | 
 | 12 | +  const sandbox = createSandbox();  | 
 | 13 | +  const testProjectId = "test-project-123";  | 
 | 14 | +  const testPlaintext = "sensitive-data-to-encrypt";  | 
 | 15 | +  const prefix = "crypto_";  | 
 | 16 | + | 
 | 17 | +  let localCrypto: LocalCrypto;  | 
 | 18 | +  let fixedCryptr: Cryptr;  | 
 | 19 | +  let projectCryptr: Cryptr;  | 
 | 20 | + | 
 | 21 | +  beforeEach(() => {  | 
 | 22 | +    localCrypto = new LocalCrypto(testProjectId);  | 
 | 23 | +    fixedCryptr = new Cryptr("teamsfx_global_key");  | 
 | 24 | +    projectCryptr = new Cryptr(testProjectId + "_teamsfx");  | 
 | 25 | +  });  | 
 | 26 | + | 
 | 27 | +  afterEach(() => {  | 
 | 28 | +    sandbox.restore();  | 
 | 29 | +  });  | 
 | 30 | + | 
 | 31 | +  describe("encrypt", () => {  | 
 | 32 | +    it("should encrypt plaintext with fixed global key and add prefix", () => {  | 
 | 33 | +      const result = localCrypto.encrypt(testPlaintext);  | 
 | 34 | + | 
 | 35 | +      assert.isTrue(result.isOk());  | 
 | 36 | +      if (result.isOk()) {  | 
 | 37 | +        const encrypted = result.value;  | 
 | 38 | +        assert.isTrue(encrypted.startsWith(prefix));  | 
 | 39 | + | 
 | 40 | +        const encryptedData = encrypted.substr(prefix.length);  | 
 | 41 | +        const decrypted = fixedCryptr.decrypt(encryptedData);  | 
 | 42 | +        assert.equal(decrypted, testPlaintext);  | 
 | 43 | +      }  | 
 | 44 | +    });  | 
 | 45 | +  });  | 
 | 46 | + | 
 | 47 | +  describe("decrypt", () => {  | 
 | 48 | +    it("should decrypt strings encrypted with fixed global key (new encryption)", () => {  | 
 | 49 | +      const encrypted = fixedCryptr.encrypt(testPlaintext);  | 
 | 50 | +      const ciphertext = prefix + encrypted;  | 
 | 51 | + | 
 | 52 | +      const result = localCrypto.decrypt(ciphertext);  | 
 | 53 | + | 
 | 54 | +      assert.isTrue(result.isOk());  | 
 | 55 | +      if (result.isOk()) {  | 
 | 56 | +        assert.equal(result.value, testPlaintext);  | 
 | 57 | +      }  | 
 | 58 | +    });  | 
 | 59 | + | 
 | 60 | +    it("should decrypt strings encrypted with project-specific key (old encryption fallback)", () => {  | 
 | 61 | +      const encrypted = projectCryptr.encrypt(testPlaintext);  | 
 | 62 | +      const ciphertext = prefix + encrypted;  | 
 | 63 | + | 
 | 64 | +      const result = localCrypto.decrypt(ciphertext);  | 
 | 65 | + | 
 | 66 | +      assert.isTrue(result.isOk());  | 
 | 67 | +      if (result.isOk()) {  | 
 | 68 | +        assert.equal(result.value, testPlaintext);  | 
 | 69 | +      }  | 
 | 70 | +    });  | 
 | 71 | + | 
 | 72 | +    it("should return error when both fixed and project cryptr fail to decrypt", () => {  | 
 | 73 | +      const invalidCiphertext = prefix + "invalid-cipher-text-that-cannot-be-decrypted";  | 
 | 74 | + | 
 | 75 | +      const result = localCrypto.decrypt(invalidCiphertext);  | 
 | 76 | + | 
 | 77 | +      assert.isTrue(result.isErr());  | 
 | 78 | +      if (result.isErr()) {  | 
 | 79 | +        assert.instanceOf(result.error, SystemError);  | 
 | 80 | +        assert.equal(result.error.source, "Core");  | 
 | 81 | +        assert.equal(result.error.name, "DecryptionError");  | 
 | 82 | +        assert.equal(result.error.message, "Cipher text is broken");  | 
 | 83 | +      }  | 
 | 84 | +    });  | 
 | 85 | + | 
 | 86 | +    it("should successfully encrypt and decrypt data (round trip)", () => {  | 
 | 87 | +      const encryptResult = localCrypto.encrypt(testPlaintext);  | 
 | 88 | +      assert.isTrue(encryptResult.isOk());  | 
 | 89 | + | 
 | 90 | +      if (encryptResult.isOk()) {  | 
 | 91 | +        const decryptResult = localCrypto.decrypt(encryptResult.value);  | 
 | 92 | +        assert.isTrue(decryptResult.isOk());  | 
 | 93 | + | 
 | 94 | +        if (decryptResult.isOk()) {  | 
 | 95 | +          assert.equal(decryptResult.value, testPlaintext);  | 
 | 96 | +        }  | 
 | 97 | +      }  | 
 | 98 | +    });  | 
 | 99 | + | 
 | 100 | +    it("should handle cross-project compatibility for new encryption", () => {  | 
 | 101 | +      const crypto1 = new LocalCrypto("project1");  | 
 | 102 | +      const crypto2 = new LocalCrypto("project2");  | 
 | 103 | + | 
 | 104 | +      // New encryption uses fixed global key, so it works across different project IDs  | 
 | 105 | +      const encryptResult = crypto1.encrypt(testPlaintext);  | 
 | 106 | +      assert.isTrue(encryptResult.isOk());  | 
 | 107 | + | 
 | 108 | +      if (encryptResult.isOk()) {  | 
 | 109 | +        const decryptResult = crypto2.decrypt(encryptResult.value);  | 
 | 110 | +        assert.isTrue(decryptResult.isOk());  | 
 | 111 | + | 
 | 112 | +        if (decryptResult.isOk()) {  | 
 | 113 | +          assert.equal(decryptResult.value, testPlaintext);  | 
 | 114 | +        }  | 
 | 115 | +      }  | 
 | 116 | +    });  | 
 | 117 | + | 
 | 118 | +    it("should not decrypt legacy data from different project IDs", () => {  | 
 | 119 | +      const project1Crypto = new LocalCrypto("project1");  | 
 | 120 | +      const project2Crypto = new LocalCrypto("project2");  | 
 | 121 | +      const project1Cryptr = new Cryptr("project1_teamsfx");  | 
 | 122 | + | 
 | 123 | +      // Create legacy encrypted data with project1 key  | 
 | 124 | +      const legacyEncrypted = prefix + project1Cryptr.encrypt(testPlaintext);  | 
 | 125 | + | 
 | 126 | +      // project1 crypto should decrypt it (fallback to project-specific key)  | 
 | 127 | +      const project1Result = project1Crypto.decrypt(legacyEncrypted);  | 
 | 128 | +      assert.isTrue(project1Result.isOk());  | 
 | 129 | +      if (project1Result.isOk()) {  | 
 | 130 | +        assert.equal(project1Result.value, testPlaintext);  | 
 | 131 | +      }  | 
 | 132 | + | 
 | 133 | +      // project2 crypto should fail to decrypt it (different project key)  | 
 | 134 | +      const project2Result = project2Crypto.decrypt(legacyEncrypted);  | 
 | 135 | +      assert.isTrue(project2Result.isErr());  | 
 | 136 | +    });  | 
 | 137 | +  });  | 
 | 138 | +});  | 
0 commit comments