1 package org.jastacry;
2
3 import java.io.BufferedInputStream;
4 import java.io.BufferedOutputStream;
5 import java.io.BufferedReader;
6 import java.io.Console;
7 import java.io.File;
8 import java.io.FileInputStream;
9 import java.io.FileNotFoundException;
10 import java.io.FileOutputStream;
11 import java.io.IOException;
12 import java.io.InputStream;
13 import java.io.InputStreamReader;
14 import java.io.OutputStream;
15 import java.io.PipedInputStream;
16 import java.io.PipedOutputStream;
17 import java.nio.charset.StandardCharsets;
18 import java.util.ArrayList;
19 import java.util.List;
20 import java.util.Locale;
21 import java.util.concurrent.CountDownLatch;
22 import java.util.concurrent.Executors;
23 import java.util.concurrent.ThreadPoolExecutor;
24
25 import org.apache.logging.log4j.LogManager;
26 import org.apache.logging.log4j.Logger;
27 import org.jastacry.GlobalData.Action;
28 import org.jastacry.GlobalData.Returncode;
29 import org.jastacry.layer.AbstractBasicLayer;
30 import org.jastacry.layer.AesCbcLayer;
31 import org.jastacry.layer.AesCtrLayer;
32 import org.jastacry.layer.AesEcbLayer;
33 import org.jastacry.layer.AsciiTransportLayer;
34 import org.jastacry.layer.FilemergeLayer;
35 import org.jastacry.layer.Md5DesLayer;
36 import org.jastacry.layer.RandomLayer;
37 import org.jastacry.layer.ReadWriteLayer;
38 import org.jastacry.layer.ReverseLayer;
39 import org.jastacry.layer.RotateLayer;
40 import org.jastacry.layer.TransparentLayer;
41 import org.jastacry.layer.XorLayer;
42
43
44
45
46
47
48
49
50 @SuppressWarnings("PMD.NcssCount")
51 public class Worker
52 {
53
54
55
56 private static final Logger LOGGER = LogManager.getLogger();
57
58
59
60
61 private static final int MINUMUM_THREADS = 2;
62
63
64
65
66 private static final char TOKEN_COMMENT = ';';
67
68
69
70
71 private static final int TOKENCOUNT_ONE = 1;
72
73
74
75
76 private boolean doAsciitransport;
77
78
79
80
81 private String confFilename;
82
83
84
85
86 private String inputFilename;
87
88
89
90
91 private String outputFilename;
92
93
94
95
96 private boolean isVerbose;
97
98
99
100
101 private Action action;
102
103
104
105
106 private final ThreadPoolExecutor executor;
107
108
109
110
111 private final LayerThreadFactory threadFactory;
112
113
114
115
116 public Worker()
117 {
118 LOGGER.traceEntry();
119 final int numCores = Runtime.getRuntime().availableProcessors();
120 LOGGER.trace("CPU cores available: {}", numCores);
121 final int numThreads = Math.max(numCores, MINUMUM_THREADS);
122 LOGGER.trace("Using {} threads in pool", numThreads);
123
124 this.threadFactory = new LayerThreadFactory();
125 this.executor = (ThreadPoolExecutor) Executors.newFixedThreadPool(numThreads);
126 this.executor.setThreadFactory(threadFactory);
127 LOGGER.traceExit();
128 }
129
130
131
132
133
134
135 @SuppressWarnings("squid:S4797")
136 public final int mainWork()
137 {
138 LOGGER.traceEntry();
139 final List<AbstractBasicLayer> layers = createLayers();
140
141 if (layers.isEmpty())
142 {
143 LOGGER.error("No layers defined!");
144 return LOGGER.traceExit(Returncode.RC_ERROR.getNumVal());
145 }
146
147 if (layers.size() == 1)
148 {
149 LOGGER.warn("Warning: Only one layer defined!");
150 return LOGGER.traceExit(Returncode.RC_ERROR.getNumVal());
151 }
152
153
154
155 if (doAsciitransport)
156 {
157 final AbstractBasicLayer layerEncode = new AsciiTransportLayer();
158 switch (action)
159 {
160 case ENCODE:
161 GlobalFunctions.logDebug(isVerbose, LOGGER, "add text encoding to end");
162 layers.add(layerEncode);
163 break;
164 case DECODE:
165 GlobalFunctions.logDebug(isVerbose, LOGGER, "add text encoding to beginning");
166 layers.add(0, layerEncode);
167 break;
168 case UNKOWN:
169 default:
170
171
172 LOGGER.error("unknown action '{}'", action);
173 break;
174 }
175 }
176
177 final File fileIn = new File(inputFilename);
178 final File fileOut = new File(outputFilename);
179
180 try
181 {
182 try (InputStream input = new BufferedInputStream(new FileInputStream(fileIn));
183 OutputStream output = new BufferedOutputStream(new FileOutputStream(fileOut)))
184 {
185 loopLayers(layers, input, output);
186 }
187 }
188 catch (final FileNotFoundException e)
189 {
190 LOGGER.catching(e);
191 return LOGGER.traceExit(Returncode.RC_ERROR.getNumVal());
192 }
193 catch (final IOException e)
194 {
195 LOGGER.catching(e);
196 }
197
198 if (isVerbose)
199 {
200 LOGGER.info("JaStaCry finished");
201 }
202
203 return LOGGER.traceExit(Returncode.RC_OK.getNumVal());
204 }
205
206
207
208
209
210
211 @SuppressWarnings({"squid:S3776", "squid:S4797"})
212 private List<AbstractBasicLayer> createLayers()
213 {
214 LOGGER.traceEntry();
215 final List<AbstractBasicLayer> layers = new ArrayList<>();
216
217
218 try (FileInputStream fstream = new FileInputStream(confFilename);
219 InputStreamReader isr = new InputStreamReader(fstream, StandardCharsets.UTF_8);
220 BufferedReader breader = new BufferedReader(isr))
221 {
222 String strLine;
223
224 AbstractBasicLayer layer = null;
225
226
227 while ((strLine = breader.readLine()) != null)
228 {
229 strLine = strLine.trim();
230 if (TOKEN_COMMENT == strLine.charAt(0))
231 {
232 GlobalFunctions.logDebug(isVerbose, LOGGER, "skip comment line '{}'", strLine);
233 }
234 else
235 {
236
237 String sLayer;
238 String sParams;
239
240 final String[] toks = strLine.split("\\s+");
241
242 if (TOKENCOUNT_ONE == toks.length)
243 {
244 sLayer = strLine;
245 sParams = "";
246 }
247 else
248 {
249 sLayer = toks[0];
250 sParams = toks[1];
251 GlobalFunctions.logDebug(isVerbose, LOGGER, "read config, layer={}, params={}", sLayer, sParams);
252
253
254 if (sParams.equalsIgnoreCase(org.jastacry.GlobalData.MACRO_PASSWORD))
255 {
256 sParams = readPassword(sLayer);
257 }
258 }
259
260 layer = createLayer(sLayer);
261 if (null == layer)
262 {
263 continue;
264 }
265 GlobalFunctions.logDebug(isVerbose, LOGGER, "adding layer {}", sLayer);
266
267 layer.init(sParams);
268 switch (action)
269 {
270 case ENCODE:
271 layers.add(layer);
272 break;
273 case DECODE:
274 layers.add(0, layer);
275 break;
276 case UNKOWN:
277 default:
278 LOGGER.error("unkown action {}", action);
279 break;
280 }
281 }
282 }
283
284 }
285 catch (final IOException e)
286 {
287 LOGGER.catching(e);
288 }
289
290 return LOGGER.traceExit(layers);
291 }
292
293
294
295
296
297
298
299 @SuppressWarnings("squid:S4829")
300 private String readPassword(final String layername)
301 {
302 String passwordString = "";
303 final String prompt = "Layer " + layername + " Password: ";
304 final Console console = System.console();
305
306 if (null == console)
307 {
308 LOGGER.error("No interactive console available for password entry!");
309 }
310 else
311 {
312 final char[] password = console.readPassword(prompt);
313 passwordString = new String(password);
314 }
315
316 return passwordString;
317 }
318
319
320
321
322
323
324
325 private AbstractBasicLayer createLayer(final String sName)
326 {
327 LOGGER.traceEntry(sName);
328 AbstractBasicLayer layer;
329
330 switch (sName.toLowerCase(Locale.getDefault()))
331 {
332 case GlobalData.LAYER_TRANSPARENT:
333 layer = new TransparentLayer();
334 break;
335 case GlobalData.LAYER_XOR:
336 layer = new XorLayer();
337 break;
338 case GlobalData.LAYER_ROTATE:
339 layer = new RotateLayer();
340 break;
341 case GlobalData.LAYER_REVERSE:
342 layer = new ReverseLayer();
343 break;
344 case GlobalData.LAYER_RANDOM:
345 layer = new RandomLayer();
346 break;
347 case GlobalData.LAYER_FILEMERGE:
348 layer = new FilemergeLayer();
349 break;
350 case GlobalData.LAYER_MD5DES:
351 layer = new Md5DesLayer();
352 break;
353 case GlobalData.LAYER_AESCBC:
354 layer = new AesCbcLayer();
355 break;
356 case GlobalData.LAYER_AESECB:
357 layer = new AesEcbLayer();
358 break;
359 case GlobalData.LAYER_AESCTR:
360 layer = new AesCtrLayer();
361 break;
362 default:
363 LOGGER.error("unknown layer '{}'", sName);
364 layer = null;
365 break;
366 }
367
368 return LOGGER.traceExit(layer);
369 }
370
371
372
373
374
375
376
377
378
379 @SuppressWarnings("squid:S2093")
380 private void loopLayers(final List<AbstractBasicLayer> layers, final InputStream input, final OutputStream output)
381 {
382 LOGGER.traceEntry();
383
384 final CountDownLatch endController = new CountDownLatch(layers.size() + 2);
385 final List<AbstractBasicLayer> layersWithIo = new ArrayList<>();
386 final List<InputStream> inputStreams = new ArrayList<>();
387 final List<OutputStream> outputStreams = new ArrayList<>();
388
389 AbstractBasicLayer l = null;
390 PipedOutputStream prevOutput = null;
391 PipedOutputStream pipedOutputFromFile = null;
392 PipedInputStream pipedInputStream = null;
393 PipedOutputStream pipedOutputStream = null;
394 PipedInputStream pipedInputStreamToFile = null;
395
396 try
397 {
398
399 pipedOutputFromFile = createOutputPipe();
400 outputStreams.add(pipedOutputFromFile);
401 l = new ReadWriteLayer();
402 l.setInputStream(input);
403 l.setOutputStream(pipedOutputFromFile);
404 l.setAction(action);
405 l.setEndController(endController);
406 layersWithIo.add(l);
407
408
409 l = layers.get(0);
410 GlobalFunctions.logDebug(isVerbose, LOGGER, "layer FIRST '{}'", l);
411 pipedInputStream = createInputPipe();
412 pipedOutputStream = createOutputPipe();
413 inputStreams.add(pipedInputStream);
414 outputStreams.add(pipedOutputStream);
415 pipedInputStream.connect(pipedOutputFromFile);
416 prevOutput = pipedOutputStream;
417 l.setInputStream(pipedInputStream);
418 l.setOutputStream(pipedOutputStream);
419 l.setAction(action);
420 l.setEndController(endController);
421 layersWithIo.add(l);
422
423
424 for (int i = 1; i < layers.size(); i++)
425 {
426 l = layers.get(i);
427
428 GlobalFunctions.logDebug(isVerbose, LOGGER, "layer {} '{}'", i, l);
429
430 pipedInputStream = createInputPipe();
431 pipedOutputStream = createOutputPipe();
432 inputStreams.add(pipedInputStream);
433 outputStreams.add(pipedOutputStream);
434 pipedInputStream.connect(prevOutput);
435 prevOutput = pipedOutputStream;
436 l.setInputStream(pipedInputStream);
437 l.setOutputStream(pipedOutputStream);
438 l.setAction(action);
439 l.setEndController(endController);
440 layersWithIo.add(l);
441 }
442
443
444 pipedInputStreamToFile = createInputPipe();
445 inputStreams.add(pipedInputStreamToFile);
446 pipedInputStreamToFile.connect(prevOutput);
447 l = new ReadWriteLayer();
448 l.setInputStream(pipedInputStreamToFile);
449 l.setOutputStream(output);
450 l.setAction(action);
451 l.setEndController(endController);
452 layersWithIo.add(l);
453
454
455 for (int i = 0; i < layersWithIo.size(); i++)
456 {
457 GlobalFunctions.logDebug(isVerbose, LOGGER, "start thread {}", i);
458 final AbstractBasicLayer layer = layersWithIo.get(i);
459 threadFactory.setNumber(i);
460 executor.execute(layer);
461 }
462
463
464 waitForThreads(endController);
465
466 }
467 catch (final IOException e)
468 {
469 LOGGER.catching(e);
470 }
471 finally
472 {
473 try
474 {
475 for (final InputStream inputStream : inputStreams)
476 {
477 inputStream.close();
478 }
479 for (final OutputStream outputStream : outputStreams)
480 {
481 outputStream.close();
482 }
483 inputStreams.clear();
484 outputStreams.clear();
485 }
486 catch (final IOException e)
487 {
488 LOGGER.catching(e);
489 }
490 }
491
492 LOGGER.traceExit();
493 }
494
495
496
497
498
499
500 private PipedInputStream createInputPipe()
501 {
502 return new PipedInputStream();
503 }
504
505
506
507
508
509
510 private PipedOutputStream createOutputPipe()
511 {
512 return new PipedOutputStream();
513 }
514
515
516
517
518
519
520 private void waitForThreads(final CountDownLatch endController)
521 {
522 try
523 {
524 endController.await();
525 }
526 catch (final InterruptedException e)
527 {
528 LOGGER.catching(e);
529 Thread.currentThread().interrupt();
530 }
531 }
532
533
534
535
536 public final void destroy()
537 {
538 executor.shutdown();
539 }
540
541
542
543
544
545
546 public final void setDoAsciitransport(final boolean bStatus)
547 {
548 doAsciitransport = bStatus;
549 }
550
551
552
553
554
555
556 public final void setConfFilename(final String sFilename)
557 {
558 confFilename = sFilename;
559 }
560
561
562
563
564
565
566 public final void setInputFilename(final String sFilename)
567 {
568 inputFilename = sFilename;
569 }
570
571
572
573
574
575
576 public final void setOutputFilename(final String sFilename)
577 {
578 outputFilename = sFilename;
579 }
580
581
582
583
584
585
586 public final void setVerbose(final boolean bStatus)
587 {
588 isVerbose = bStatus;
589 }
590
591
592
593
594
595
596 public final void setAction(final Action oAction)
597 {
598 this.action = oAction;
599 }
600
601 }