View Javadoc
1   package org.apache.fulcrum.jce.crypto.cli;
2   
3   /*
4    * Licensed to the Apache Software Foundation (ASF) under one
5    * or more contributor license agreements.  See the NOTICE file
6    * distributed with this work for additional information
7    * regarding copyright ownership.  The ASF licenses this file
8    * to you under the Apache License, Version 2.0 (the
9    * "License"); you may not use this file except in compliance
10   * with the License.  You may obtain a copy of the License at
11   *
12   *   http://www.apache.org/licenses/LICENSE-2.0
13   *
14   * Unless required by applicable law or agreed to in writing,
15   * software distributed under the License is distributed on an
16   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
17   * KIND, either express or implied.  See the License for the
18   * specific language governing permissions and limitations
19   * under the License.
20   */
21  
22  import java.io.ByteArrayInputStream;
23  import java.io.ByteArrayOutputStream;
24  import java.io.File;
25  import java.io.FileInputStream;
26  import java.io.FileOutputStream;
27  import java.io.OutputStreamWriter;
28  import java.nio.charset.Charset;
29  import java.util.Arrays;
30  import java.util.LinkedHashSet;
31  import java.util.List;
32  import java.util.Optional;
33  import java.util.Set;
34  import java.util.regex.Matcher;
35  import java.util.regex.Pattern;
36  import java.util.stream.Collectors;
37  
38  import org.apache.fulcrum.jce.crypto.HexConverter;
39  import org.apache.fulcrum.jce.crypto.StreamUtil;
40  import org.apache.fulcrum.jce.crypto.extended.CryptoParametersJ8;
41  import org.apache.fulcrum.jce.crypto.extended.CryptoParametersJ8.TYPES;
42  import org.apache.fulcrum.jce.crypto.extended.CryptoStreamFactoryJ8Template;
43  import org.apache.fulcrum.jce.crypto.extended.CryptoUtilJ8;
44  
45  /**
46   * <b>Manifest main class</b>.
47   * 
48   * Command line tool for encrypting/decrypting a file or string
49   *
50   * file [enc|dec] passwd [file]* string [enc|dec] passwd plaintext
51   * 
52   * Example :
53   * 
54   * <pre>
55   * java -classpath target/classes org.apache.fulcrum.jce.crypto.cli.CLI2 string enc changeit mysecretgeheim
56   * </pre>
57   * 
58   * <pre>
59   * java -jar target/fulcrum-yaafi-crypto-1.0.8.jar string enc changeit mysecretgeheim
60   * </pre>
61   * 
62   * ...
63   * 
64   * <pre>
65   * java java -jar target/fulcrum-yaafi-crypto-1.0.8.jar string dec changeit anothersecret
66   * </pre>
67   * 
68   *  @author gk@apache.org
69   *
70   */
71  public class CLI2 {
72  	
73  	
74  	static boolean debug = false;
75  	/**
76  	 * Allows usage on the command line.
77  	 * 
78  	 * @param args the command line parameters
79  	 */
80  	public static void main(String[] args) {
81  		try {
82  			if (args.length == 0) {
83  				printHelp();
84  				return;
85  			}
86  			String operationMode = args[0];
87  
88  			String msg = "No operationMode";
89  			if (operationMode == null || operationMode.equals("")) {
90  				throw new IllegalArgumentException(msg);
91  			}
92  
93  			if (operationMode.equals("info")) {
94  				printInfo();
95  				return;
96  			} else if (operationMode.equals("help")) {
97  				printHelp();
98  				return;
99  			}
100 
101 			if (args.length < 3) {
102 				printHelp();
103 				throw new IllegalArgumentException("Invalid command line");
104 			}
105 
106 			if (operationMode.equals("file")) {
107 				processFiles(args);
108 			} else if (operationMode.equals("string")) {
109 				processString(args);
110 			}
111 		} catch (Exception e) {
112 			System.out.println("Error: " + e.getMessage());
113 			e.printStackTrace();
114 		}
115 	}
116 
117 	private static void printInfo() {
118 		CryptoUtilJ8 cryptoUtilJ8 = CryptoUtilJ8.getInstance();
119 		System.out.println("");
120 		System.out.println("\t| Default Crypto factory class: \t" + cryptoUtilJ8.getCryptoStreamFactory().getClass());
121 		System.out.println("\t|_Default Algorithm used: \t" + cryptoUtilJ8.getCryptoStreamFactory().getAlgorithm());
122 		
123 		for (int i = 0; i < CryptoParametersJ8.LISTS.length; i++) {
124 			List<String> list =  CryptoParametersJ8.LISTS[i];
125 			String type =  CryptoParametersJ8.PROVIDER_TYPES[i];
126 			System.out.println("\t|Algorithms (unchecked) supported: \t" + list);	
127 			List<String> result = CryptoParametersJ8.getSupportedAlgos(list, type, true);
128 			Set<String> resultSet = new LinkedHashSet<String>(result);
129 			resultSet.addAll( CryptoParametersJ8.getSupportedAlgos(list, type, false) );
130 			System.out.println(
131 					String.format("\t|_Matched supported %2$s:\t%1$s", 
132 							(resultSet), type));
133 		}
134 
135 		List<String> supportedAlgos = CryptoParametersJ8.init();
136 		System.out.println(
137 				String.format("\t|Effectively supported from %2$ss:\t*** %1$s ***", 
138 						supportedAlgos, Arrays.toString( CryptoParametersJ8.PROVIDER_TYPES) ));		
139 		
140 		if (debug) {
141 			Arrays.stream(CryptoParametersJ8.TYPES.values()).forEach(t -> {
142 				CryptoUtilJ8 testcu = CryptoUtilJ8.getInstance(t);
143 				System.out.println("\t| Crypto factory class: \t" + testcu.getCryptoStreamFactory().getClass());
144 				System.out.println("\t|_Algorithm used: \t" + testcu.getCryptoStreamFactory().getAlgorithm());
145 
146 			});
147 		}
148 		System.out.println(
149 				"\t|_ More Info: https://docs.oracle.com/javase/8/docs/technotes/guides/security/StandardNames.html\r\n");
150 	}
151 	
152 
153 	/**
154 	 * Prints usage information.
155 	 */
156 	public static void printHelp() {
157 		System.out.println(
158 				"\r\n\t*** Command line tool for encrypting/decrypting strings/files ***\r\n\t*** algorithm based on "
159 						+ CryptoParametersJ8.TYPES_IMPL.ALGORITHM_J8_PBE + "***\r\n");
160 		System.out.println("\tjava -cp target\\classes " + CLI2.class.getName()
161 				+ " <operation mode> <coding mode> <password> <path|string> [target]\r\n");
162 		System.out.println(
163 				"\tjava -jar target/fulcrum-yaafi-crypto-1.0.8-SNAPSHOT.jar <operation mode> <coding mode> <password> <path|string> [target]\r\n");
164 		System.out.println("\t-------------------");
165 		System.out.println("\toperation mode: file|string|info");
166 		System.out.println("\tcoding mode: enc|dec|enc:GCM. Default algorithm is " + CryptoParametersJ8.DEFAULT_TYPE);
167 		System.out.println("\t<password: string or empty:''");
168 		System.out.println("\tcode|coderef: path|string");
169 		System.out.println("\ttarget: optional\r\n");
170 		System.out.println("\t-------------------");
171 		System.out.println("\t*** Usage: ***\r\n");
172 		System.out.println("\t" + CLI2.class.getSimpleName() + " file [enc|dec] passwd source [target]");
173 		System.out.println("\t" + CLI2.class.getSimpleName() + " string [enc|dec] passwd source");
174 		System.out.println("\t" + CLI2.class.getSimpleName() + " info");
175 	}
176 
177 	/**
178 	 * Decrypt/encrypt a list of files
179 	 * 
180 	 * @param args the command line
181 	 * @throws Exception the operation failed
182 	 */
183 	public static void processFiles(String[] args) throws Exception {
184 		String cipherMode = args[1];
185 		char[] password = args[2].toCharArray();
186 		File sourceFile = new File(args[3]);
187 		File targetFile = null;
188 
189 		if (args.length == 4) {
190 			targetFile = sourceFile;
191 		} else {
192 			targetFile = new File(args[4]);
193 			File parentFile = targetFile.getParentFile();
194 
195 			if (parentFile != null && (!parentFile.exists() || !parentFile.isDirectory())) {
196 				boolean success = parentFile.mkdirs();
197 				if (!success) {
198 					System.err.println("Error, could not create directory to write parent file");
199 				}
200 			}
201 		}
202 
203 		processFile(cipherMode, password, sourceFile, targetFile);
204 	}
205 
206 	/**
207 	 * Decrypt/encrypt a single file
208 	 * 
209 	 * @param cipherMode the mode
210 	 * @param password   the password
211 	 * @param sourceFile the file to process
212 	 * @param targetFile the target file
213 	 * @throws Exception the operation failed
214 	 */
215 	public static void processFile(String cipherMode, char[] password, File sourceFile, File targetFile)
216 			throws Exception {
217 
218 		try (FileInputStream fis = new FileInputStream(sourceFile)) {
219 			ByteArrayOutputStream baos = new ByteArrayOutputStream();
220 			CryptoUtilJ8 cryptoUtilJ8 = createCryptoUtil(cipherMode);
221 			
222 			if (cryptoUtilJ8 == null) {
223 				System.out.println("Canceling ");
224 				return;
225 			}
226 
227 			if (cipherMode.startsWith("dec")) {
228 				System.out.println("Decrypting " + sourceFile.getAbsolutePath());
229 
230 				// String value = new String(Files.readAllBytes(Paths.get(sourceFile.toURI())));
231 				StringBuffer stringBuffer = new StringBuffer();
232 				int i;
233 				while ((i = fis.read()) != -1) {
234 					stringBuffer.append((char) i);
235 				}
236 
237 				String value = stringBuffer.toString();
238 				if (isHexadecimal(value)) {
239 					byte[] buffer = HexConverter.toBytes(value);
240 					cryptoUtilJ8.decrypt(buffer, baos, password);
241 				} else {
242 					try (FileInputStream fis2 = new FileInputStream(sourceFile)) {
243 						cryptoUtilJ8.decrypt(fis2, baos, password);
244 					}
245 				}
246 
247 				ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
248 				FileOutputStream fos = new FileOutputStream(targetFile);
249 				StreamUtil.copy(bais, fos);
250 				bais.close();
251 				fos.close();
252 			} else if (cipherMode.startsWith("enc")) {
253 				System.out.println("Encrypting " + sourceFile.getAbsolutePath());
254 				cryptoUtilJ8.encrypt(fis, baos, password);
255 				fis.close();
256 
257 				ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
258 				FileOutputStream fos = new FileOutputStream(targetFile);
259 				StreamUtil.copy(bais, fos);
260 				bais.close();
261 				fos.close();
262 			} else {
263 				String msg = "Don't know what to do with : " + cipherMode;
264 				throw new IllegalArgumentException(msg);
265 			}
266 		}
267 	}
268 
269 	private static CryptoUtilJ8 createCryptoUtil(String cipherMode) throws Exception {
270 		CryptoUtilJ8 cryptoUtilJ8 = null;
271 		List<String> supportedTypes = CryptoParametersJ8.init();
272 		// no extension like enc:GCM
273 		if (cipherMode.substring("enc".length()).equals("")) {
274 			if (supportedTypes.stream().anyMatch(x-> x.equals(CryptoParametersJ8.DEFAULT_TYPE.toString()) )) {
275 				System.err.println("using default type:"+ CryptoParametersJ8.DEFAULT_TYPE);
276 				cryptoUtilJ8 = CryptoUtilJ8.getInstance();
277 			} else {
278 				System.err.println("Could not use default type:"+ TYPES.PBE + ".You have to set explicit type, e.g. enc:"+supportedTypes.get(0) );
279 			}
280 		} else {
281 			System.err.println("checking supported types:"+ supportedTypes);
282 			List<String> matchedType = supportedTypes.stream().filter(x-> cipherMode.endsWith(x) ).collect(Collectors.toList());
283 			System.err.println("matched type:"+ matchedType);
284 			Optional<TYPES> algoShortcut = Arrays.stream(CryptoParametersJ8.TYPES.values())
285 					.filter(a -> matchedType.get(0).equals(a.toString())).findFirst();
286 			if (algoShortcut.isPresent()) {
287 				System.err.println("initializing type:"+ algoShortcut);
288 				cryptoUtilJ8 = CryptoUtilJ8.getInstance(algoShortcut.get());
289 			}
290 		}
291 
292 		if (cryptoUtilJ8 == null) {
293 			throw new Exception("Could not use algorithm. Check debug output and JDK provided algo shortcuts with CLI2 info!");
294 		}
295 		
296 		if (debug) {
297 			CryptoStreamFactoryJ8Template crt = ((CryptoStreamFactoryJ8Template)cryptoUtilJ8.getCryptoStreamFactory());
298 			System.err.println(String.format("using crypto factory instance %s for algo %s and type %s with salt length: %s and count %s", 
299 	           		crt.getClass().getSimpleName(), crt.getType(),
300 	           		crt.getAlgorithm(), crt.getSalt().length, crt.getCount()));
301 		}
302 		return cryptoUtilJ8;
303 	}
304 
305 	/**
306 	 * Decrypt and encrypt a string.
307 	 * 
308 	 * @param args the command line
309 	 * @throws Exception the operation failed
310 	 */
311 	public static void processString(String[] args) throws Exception {
312 		final String cipherMode;
313 		final char[] password;
314 		final String value;
315 		File targetFile = null;
316 		if (args.length > 3) {
317 			cipherMode = args[1];
318 			password = args[2].toCharArray();
319 			value = args[3];
320 		} else {
321 			value = null;
322 			cipherMode = null;
323 			password = null;
324 		}
325 		if (args.length == 5) {
326 			targetFile = new File(args[4]);
327 			File parentFile = targetFile.getParentFile();
328 
329 			if (parentFile != null && (!parentFile.exists() || !parentFile.isDirectory())) {
330 				boolean success = parentFile.mkdirs();
331 				if (!success) {
332 					System.err.println("Error, could not create directory to write parent file");
333 				}
334 			}
335 		}
336 
337 		if (value != null && !value.equals("")) {
338 
339 			String result = processString(cipherMode, password, value);
340 
341 			if (targetFile != null) {
342 
343 				try (OutputStreamWriter osw = new OutputStreamWriter(new FileOutputStream(targetFile),
344 						Charset.forName("UTF-8").newEncoder())) {
345 					osw.write(result);
346 				}
347 			} else {
348 				System.out.println(result);
349 			}
350 		}
351 	}
352 
353 	/**
354 	 * Decrypt and encrypt a string.
355 	 * 
356 	 * @param cipherMode \"dec|enc\" + @link{TYPES}
357 	 * @param password   as char array
358 	 * @param value      String to be en/decrypted
359 	 * @throws Exception the operation failed
360 	 * 
361 	 * @return the result - either the encrypted or decrypted string depending on
362 	 *         cipherMode
363 	 */
364 	public static String processString(String cipherMode, char[] password, String value) throws Exception {
365 		if (value != null && !value.equals("")) {
366 			CryptoUtilJ8 cryptoUtilJ8 = createCryptoUtil(cipherMode);
367 
368 			String result = null;
369 			if (cipherMode.startsWith("dec")) {
370 				result = cryptoUtilJ8.decryptString(value, password);
371 			} else if (cipherMode.startsWith("enc")) {
372 				result = cryptoUtilJ8.encryptString(value, password);
373 			}
374 			return result;
375 		} else {
376 			return null;
377 		}
378 	}
379 
380 	private static final Pattern HEXADECIMAL_PATTERN = Pattern.compile("\\p{XDigit}+");
381 
382 	public static boolean isHexadecimal(String input) {
383 		final Matcher matcher = HEXADECIMAL_PATTERN.matcher(input);
384 		return matcher.matches();
385 	}
386 }