StringArrayStorage.ts 11 KB

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