StringArrayStorage.ts 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316
  1. import { inject, injectable, postConstruct } from 'inversify';
  2. import { ServiceIdentifiers } from '../../container/ServiceIdentifiers';
  3. import { TIdentifierNamesGeneratorFactory } from '../../types/container/generators/TIdentifierNamesGeneratorFactory';
  4. import { IIdentifierNamesGenerator } from '../../interfaces/generators/identifier-names-generators/IIdentifierNamesGenerator';
  5. import { IArrayUtils } from '../../interfaces/utils/IArrayUtils';
  6. import { ICryptUtils } from '../../interfaces/utils/ICryptUtils';
  7. import { IEncodedValue } from '../../interfaces/IEncodedValue';
  8. import { IEscapeSequenceEncoder } from '../../interfaces/utils/IEscapeSequenceEncoder';
  9. import { IOptions } from '../../interfaces/options/IOptions';
  10. import { IRandomGenerator } from '../../interfaces/utils/IRandomGenerator';
  11. import { IStringArrayStorage } from '../../interfaces/storages/string-array-storage/IStringArrayStorage';
  12. import { IStringArrayStorageItemData } from '../../interfaces/storages/string-array-storage/IStringArrayStorageItem';
  13. import { initializable } from '../../decorators/Initializable';
  14. import { StringArrayEncoding } from '../../enums/StringArrayEncoding';
  15. import { MapStorage } from '../MapStorage';
  16. @injectable()
  17. export class StringArrayStorage extends MapStorage <string, IStringArrayStorageItemData> implements IStringArrayStorage {
  18. /**
  19. * @type {number}
  20. */
  21. private static readonly minimumRotationAmount: number = 100;
  22. /**
  23. * @type {number}
  24. */
  25. private static readonly maximumRotationAmount: number = 500;
  26. /**
  27. * @type {number}
  28. */
  29. private static readonly rc4KeyLength: number = 4;
  30. /**
  31. * @type {number}
  32. */
  33. private static readonly rc4KeysCount: number = 50;
  34. /**
  35. * @type {number}
  36. */
  37. private static readonly stringArrayNameLength: number = 7;
  38. /**
  39. * @type {IArrayUtils}
  40. */
  41. private readonly arrayUtils: IArrayUtils;
  42. /**
  43. * @type {ICryptUtils}
  44. */
  45. private readonly cryptUtils: ICryptUtils;
  46. /**
  47. * @type {IEscapeSequenceEncoder}
  48. */
  49. private readonly escapeSequenceEncoder: IEscapeSequenceEncoder;
  50. /**
  51. * @type {IIdentifierNamesGenerator}
  52. */
  53. private readonly identifierNamesGenerator: IIdentifierNamesGenerator;
  54. /**
  55. * @type {string[]}
  56. */
  57. private readonly rc4Keys: string[];
  58. /**
  59. * @type {Map<string, string[]>}
  60. */
  61. private readonly rc4EncodedValuesSourcesCache: Map<string, string[]> = new Map();
  62. /**
  63. * @type {number}
  64. */
  65. private rotationAmount: number = 0;
  66. /**
  67. * @type {string}
  68. */
  69. @initializable()
  70. private stringArrayStorageName!: string;
  71. /**
  72. * @type {string}
  73. */
  74. @initializable()
  75. private stringArrayStorageCallsWrapperName!: string;
  76. /**
  77. * @param {TIdentifierNamesGeneratorFactory} identifierNamesGeneratorFactory
  78. * @param {IArrayUtils} arrayUtils
  79. * @param {IRandomGenerator} randomGenerator
  80. * @param {IOptions} options
  81. * @param {ICryptUtils} cryptUtils
  82. * @param {IEscapeSequenceEncoder} escapeSequenceEncoder
  83. */
  84. public constructor (
  85. @inject(ServiceIdentifiers.Factory__IIdentifierNamesGenerator)
  86. identifierNamesGeneratorFactory: TIdentifierNamesGeneratorFactory,
  87. @inject(ServiceIdentifiers.IArrayUtils) arrayUtils: IArrayUtils,
  88. @inject(ServiceIdentifiers.IRandomGenerator) randomGenerator: IRandomGenerator,
  89. @inject(ServiceIdentifiers.IOptions) options: IOptions,
  90. @inject(ServiceIdentifiers.ICryptUtils) cryptUtils: ICryptUtils,
  91. @inject(ServiceIdentifiers.IEscapeSequenceEncoder) escapeSequenceEncoder: IEscapeSequenceEncoder
  92. ) {
  93. super(randomGenerator, options);
  94. this.identifierNamesGenerator = identifierNamesGeneratorFactory(options);
  95. this.arrayUtils = arrayUtils;
  96. this.cryptUtils = cryptUtils;
  97. this.escapeSequenceEncoder = escapeSequenceEncoder;
  98. this.rc4Keys = this.randomGenerator.getRandomGenerator()
  99. .n(
  100. () => this.randomGenerator.getRandomGenerator().string({
  101. length: StringArrayStorage.rc4KeyLength
  102. }),
  103. StringArrayStorage.rc4KeysCount
  104. );
  105. }
  106. @postConstruct()
  107. public initialize (): void {
  108. super.initialize();
  109. const baseStringArrayName: string = this.identifierNamesGenerator
  110. .generate(StringArrayStorage.stringArrayNameLength);
  111. const baseStringArrayCallsWrapperName: string = this.identifierNamesGenerator
  112. .generate(StringArrayStorage.stringArrayNameLength);
  113. this.stringArrayStorageName = `${this.options.identifiersPrefix}${baseStringArrayName}`;
  114. this.stringArrayStorageCallsWrapperName = `${this.options.identifiersPrefix}${baseStringArrayCallsWrapperName}`;
  115. this.rotationAmount = this.options.rotateStringArray
  116. ? this.randomGenerator.getRandomInteger(
  117. StringArrayStorage.minimumRotationAmount,
  118. StringArrayStorage.maximumRotationAmount
  119. )
  120. : 0;
  121. }
  122. /**
  123. * @param {string} value
  124. * @returns {IStringArrayStorageItemData}
  125. */
  126. public get (value: string): IStringArrayStorageItemData {
  127. return this.getOrSetIfDoesNotExist(value);
  128. }
  129. /**
  130. * @returns {number}
  131. */
  132. public getRotationAmount (): number {
  133. return this.rotationAmount;
  134. }
  135. /**
  136. * @returns {string}
  137. */
  138. public getStorageId (): string {
  139. return this.stringArrayStorageName;
  140. }
  141. /**
  142. * @returns {string}
  143. */
  144. public getStorageName (): string {
  145. return this.getStorageId();
  146. }
  147. /**
  148. * @returns {string}
  149. */
  150. public getStorageCallsWrapperName (): string {
  151. return this.stringArrayStorageCallsWrapperName;
  152. }
  153. public rotateStorage (): void {
  154. if (!this.getLength()) {
  155. return;
  156. }
  157. this.storage = new Map(
  158. this.arrayUtils.rotate(
  159. Array.from(this.storage.entries()),
  160. this.rotationAmount
  161. )
  162. );
  163. }
  164. public shuffleStorage (): void {
  165. this.storage = new Map(
  166. this.arrayUtils
  167. .shuffle(Array.from(this.storage.entries()))
  168. .map<[string, IStringArrayStorageItemData]>(
  169. (
  170. [value, stringArrayStorageItemData]: [string, IStringArrayStorageItemData],
  171. index: number
  172. ) => {
  173. stringArrayStorageItemData.index = index;
  174. return [value, stringArrayStorageItemData];
  175. }
  176. )
  177. .sort((
  178. [, stringArrayStorageItemDataA]: [string, IStringArrayStorageItemData],
  179. [, stringArrayStorageItemDataB]: [string, IStringArrayStorageItemData]
  180. ) => stringArrayStorageItemDataA.index - stringArrayStorageItemDataB.index)
  181. );
  182. }
  183. /**
  184. * @returns {string}
  185. */
  186. public toString (): string {
  187. return Array
  188. .from(this.storage.values())
  189. .map((stringArrayStorageItemData: IStringArrayStorageItemData) => {
  190. // we have to encode here, because of possible errors during `parse` of StringArrayCustomNode
  191. return `'${this.escapeSequenceEncoder.encode(
  192. stringArrayStorageItemData.encodedValue,
  193. this.options.unicodeEscapeSequence
  194. )}'`;
  195. }).toString();
  196. }
  197. /**
  198. * @param {string} value
  199. * @returns {IStringArrayStorageItemData}
  200. */
  201. private getOrSetIfDoesNotExist (value: string): IStringArrayStorageItemData {
  202. const { encodedValue, decodeKey }: IEncodedValue = this.getEncodedValue(value);
  203. const storedStringArrayStorageItemData: IStringArrayStorageItemData | undefined = this.storage.get(encodedValue);
  204. if (storedStringArrayStorageItemData) {
  205. return storedStringArrayStorageItemData;
  206. }
  207. const stringArrayStorageItemData: IStringArrayStorageItemData = {
  208. encodedValue,
  209. decodeKey,
  210. value,
  211. index: this.getLength()
  212. };
  213. this.storage.set(encodedValue, stringArrayStorageItemData);
  214. return stringArrayStorageItemData;
  215. }
  216. /**
  217. * @param {string} value
  218. * @returns {IEncodedValue}
  219. */
  220. private getEncodedValue (value: string): IEncodedValue {
  221. switch (this.options.stringArrayEncoding) {
  222. /**
  223. * For rc4 there is a possible chance of a collision between encoded values that were received from
  224. * different source values with different keys
  225. *
  226. * For example:
  227. * source value | key | encoded value
  228. * _15 | CRDL | w74TGA==
  229. * _12 | q9mB | w74TGA==
  230. *
  231. * Issue: https://github.com/javascript-obfuscator/javascript-obfuscator/issues/538
  232. *
  233. * As a fix that keeps key size of 4 character, the simple brute-force solution is using:
  234. * if collision will happen, just try to encode value again
  235. */
  236. case StringArrayEncoding.Rc4: {
  237. const decodeKey: string = this.randomGenerator.getRandomGenerator().pickone(this.rc4Keys);
  238. const encodedValue: string = this.cryptUtils.btoa(this.cryptUtils.rc4(value, decodeKey));
  239. const encodedValueSources: string[] = this.rc4EncodedValuesSourcesCache.get(encodedValue) ?? [];
  240. let encodedValueSourcesLength: number = encodedValueSources.length;
  241. const shouldAddValueToSourcesCache: boolean = !encodedValueSourcesLength || !encodedValueSources.includes(value);
  242. if (shouldAddValueToSourcesCache) {
  243. encodedValueSources.push(value);
  244. encodedValueSourcesLength++;
  245. }
  246. this.rc4EncodedValuesSourcesCache.set(encodedValue, encodedValueSources);
  247. if (encodedValueSourcesLength > 1) {
  248. return this.getEncodedValue(value);
  249. }
  250. return { encodedValue, decodeKey };
  251. }
  252. case StringArrayEncoding.Base64: {
  253. const decodeKey: null = null;
  254. const encodedValue: string = this.cryptUtils.btoa(value);
  255. return { encodedValue, decodeKey };
  256. }
  257. default: {
  258. const decodeKey: null = null;
  259. const encodedValue: string = value;
  260. return { encodedValue, decodeKey };
  261. }
  262. }
  263. }
  264. }