001 package com.valhalla.misc;
002
003 import java.io.BufferedReader;
004 import java.io.BufferedWriter;
005 import java.io.File;
006 import java.io.FileWriter;
007 import java.io.IOException;
008 import java.io.InputStream;
009 import java.io.InputStreamReader;
010 import java.io.OutputStream;
011 import java.io.OutputStreamWriter;
012
013 import com.valhalla.jbother.BuddyList;
014 import com.valhalla.settings.Settings;
015
016 /**
017 * A class that implements PGP interface for Java.
018 * <P>
019 *
020 * It calls gpg (GnuPG) program to do all the PGP commands. $Id:$
021 *
022 * @author Yaniv Yemini, January 2004.
023 * @author Based on a class GnuPG by John Anderson, which can be found
024 * @author at:
025 * http://lists.gnupg.org/pipermail/gnupg-devel/2002-February/018098.html
026 * @author modified for use in JBother by Andrey Zakirov, February 2005
027 * @created March 9, 2005
028 * @version 0.5.1
029 * @see GnuPG - http://www.gnupg.org/
030 */
031
032 public class GnuPG {
033
034 // Constants:
035 private final String kGnuPGCommand;
036
037 private static final String kGnuPGArgs = " --batch --armor --output -";
038
039 // Class vars:
040 private int gpg_exitCode = -1;
041
042 private String gpg_result;
043
044 private String gpg_err;
045
046 /**
047 * Reads an output stream from an external process. Imeplemented as a thred.
048 *
049 * @author synic
050 * @created March 9, 2005
051 */
052 class ProcessStreamReader extends Thread {
053 InputStream is;
054
055 String type;
056
057 OutputStream os;
058
059 String fullLine = "";
060
061 /**
062 * Constructor for the ProcessStreamReader object
063 *
064 * @param is
065 * Description of the Parameter
066 * @param type
067 * Description of the Parameter
068 */
069 ProcessStreamReader(InputStream is, String type) {
070 this(is, type, null);
071 }
072
073 /**
074 * Constructor for the ProcessStreamReader object
075 *
076 * @param is
077 * Description of the Parameter
078 * @param type
079 * Description of the Parameter
080 * @param redirect
081 * Description of the Parameter
082 */
083 ProcessStreamReader(InputStream is, String type, OutputStream redirect) {
084 this.is = is;
085 this.type = type;
086 this.os = redirect;
087 }
088
089 /**
090 * Main processing method for the ProcessStreamReader object
091 */
092 public void run() {
093 try {
094 InputStreamReader isr = new InputStreamReader(is);
095 BufferedReader br = new BufferedReader(isr);
096 String line = null;
097 while ((line = br.readLine()) != null) {
098 fullLine = fullLine + line + "\n";
099 }
100
101 } catch (IOException ioe) {
102 ioe.printStackTrace();
103 }
104 }
105
106 /**
107 * Gets the string attribute of the ProcessStreamReader object
108 *
109 * @return The string value
110 */
111 String getString() {
112 return fullLine;
113 }
114
115 }
116
117 /**
118 * Sign
119 *
120 * @param inStr
121 * input string to sign
122 * @param secID
123 * ID of secret key to sign with
124 * @param passPhrase
125 * passphrase for the secret key to sign with
126 * @return true upon success
127 */
128 public boolean sign(String inStr, String secID, String passPhrase) {
129 boolean success = false;
130 File tmpFile = createTempFile(inStr);
131
132 if (tmpFile != null) {
133 success = runGnuPG("-u " + secID + " --passphrase-fd 0 -b "
134 + tmpFile.getAbsolutePath(), passPhrase);
135 tmpFile.delete();
136 if (success && this.gpg_exitCode != 0) {
137 success = false;
138 }
139 }
140 return success;
141 }
142
143 /**
144 * ClearSign
145 *
146 * @param inStr
147 * input string to sign
148 * @param secID
149 * ID of secret key to sign with
150 * @param passPhrase
151 * passphrase for the secret key to sign with
152 * @return true upon success
153 */
154 public boolean clearSign(String inStr, String secID, String passPhrase) {
155 boolean success = false;
156
157 File tmpFile = createTempFile(inStr);
158
159 if (tmpFile != null) {
160 success = runGnuPG("-u " + secID
161 + " --passphrase-fd 0 --clearsign "
162 + tmpFile.getAbsolutePath(), passPhrase);
163 tmpFile.delete();
164 if (success && this.gpg_exitCode != 0) {
165 success = false;
166 }
167 }
168 return success;
169 }
170
171 /**
172 * Signs and encrypts a string
173 *
174 * @param inStr
175 * input string to encrypt
176 * @param secID
177 * ID of secret key to sign with
178 * @param keyID
179 * ID of public key to encrypt with
180 * @param passPhrase
181 * passphrase for the secret key to sign with
182 * @return true upon success
183 */
184 public boolean signAndEncrypt(String inStr, String secID, String keyID,
185 String passPhrase) {
186 boolean success = false;
187
188 File tmpFile = createTempFile(inStr);
189
190 if (tmpFile != null) {
191 success = runGnuPG("-u " + secID + " -r " + keyID
192 + " --passphrase-fd 0 -se " + tmpFile.getAbsolutePath(),
193 passPhrase);
194 tmpFile.delete();
195 if (success && this.gpg_exitCode != 0) {
196 success = false;
197 }
198 }
199 return success;
200 }
201
202 /**
203 * Encrypt
204 *
205 * @param inStr
206 * input string to encrypt
207 * @param secID
208 * ID of secret key to use
209 * @param keyID
210 * ID of public key to encrypt with
211 * @return true upon success
212 */
213 public boolean encrypt(String inStr, String secID, String keyID) {
214
215 boolean success;
216 success = runGnuPG("-u " + secID + " -r " + keyID + " --encrypt", inStr);
217 if (success && this.gpg_exitCode != 0) {
218 success = false;
219 }
220 return success;
221 }
222
223 /**
224 * Decrypt
225 *
226 * @param inStr
227 * input string to decrypt
228 * @param passPhrase
229 * passphrase for the secret key to decrypt with
230 * @return true upon success
231 */
232 public boolean decrypt(String inStr, String passPhrase) {
233 boolean success = false;
234
235 File tmpFile = createTempFile(inStr);
236
237 if (tmpFile != null) {
238 success = runGnuPG("--passphrase-fd 0 --decrypt "
239 + tmpFile.getAbsolutePath(), passPhrase);
240 tmpFile.delete();
241 if (success && this.gpg_exitCode != 0) {
242 success = false;
243 }
244 }
245 return success;
246 }
247
248 /**
249 * List public keys in keyring
250 *
251 * @param ID
252 * ID of public key to list, blank for all
253 * @return true upon success
254 */
255 public boolean listKeys(String ID) {
256 boolean success;
257 success = runGnuPG("--list-keys --with-colons " + ID, null);
258 if (success && this.gpg_exitCode != 0) {
259 success = false;
260 }
261 return success;
262 }
263
264 /**
265 * List secret keys in keyring
266 *
267 * @param ID
268 * ID of secret key to list, blank for all
269 * @return true upon success
270 */
271 public boolean listSecretKeys(String ID) {
272 boolean success;
273 success = runGnuPG("--list-secret-keys --with-colons " + ID, null);
274 if (success && this.gpg_exitCode != 0) {
275 success = false;
276 }
277 return success;
278 }
279
280 /**
281 * Verify a signature
282 *
283 * @param inStr
284 * signature to verify
285 * @return true if verified.
286 */
287 public boolean verify(String signedString, String dataString) {
288 boolean success = false;
289 File signedFile = createTempFile(signedString);
290 File dataFile = createTempFile(dataString);
291
292 if ((signedFile != null) && (dataFile != null)) {
293 success = runGnuPG("--verify " + signedFile.getAbsolutePath() + " "
294 + dataFile.getAbsolutePath(), null);
295 signedFile.delete();
296 dataFile.delete();
297 if (success && this.gpg_exitCode != 0) {
298 success = false;
299 }
300 }
301 return success;
302 }
303
304 /**
305 * Get processing result
306 *
307 * @return result string.
308 */
309 public String getResult() {
310 return gpg_result;
311 }
312
313 /**
314 * Get error output from GnuPG process
315 *
316 * @return error string.
317 */
318 public String getErrorString() {
319 return gpg_err;
320 }
321
322 /**
323 * Get GnuPG exit code
324 *
325 * @return exit code.
326 */
327 public int getExitCode() {
328 return gpg_exitCode;
329 }
330
331 /**
332 * Runs GnuPG external program
333 *
334 * @param commandArgs
335 * command line arguments
336 * @param inputStr
337 * string to pass to GnuPG process
338 * @return true if success.
339 */
340 private boolean runGnuPG(String commandArgs, String inputStr) {
341 Process p;
342 String fullCommand = kGnuPGCommand + " " + commandArgs;
343 // String fullCommand = commandArgs;
344
345 try {
346 p = Runtime.getRuntime().exec(fullCommand);
347 } catch (IOException io) {
348 System.out.println("io Error " + io.getMessage());
349 com.valhalla.Logger.logException(io);
350 return false;
351 }
352 if (inputStr != null) {
353 BufferedWriter out = new BufferedWriter(new OutputStreamWriter(p
354 .getOutputStream()));
355 try {
356 out.write(inputStr);
357 out.close();
358 } catch (IOException io) {
359 System.out.println("Exception at write! " + io.getMessage());
360 return false;
361 }
362 }
363
364 ProcessStreamReader psr_stdout = new ProcessStreamReader(p
365 .getInputStream(), "ERROR");
366 ProcessStreamReader psr_stderr = new ProcessStreamReader(p
367 .getErrorStream(), "OUTPUT");
368 psr_stdout.start();
369 psr_stderr.start();
370 try {
371
372 psr_stdout.join();
373 psr_stderr.join();
374 } catch (InterruptedException i) {
375 System.out.println("Exception at join! " + i.getMessage());
376 return false;
377 }
378
379 try {
380 p.waitFor();
381
382 } catch (InterruptedException i) {
383 System.out.println("Exception at waitfor! " + i.getMessage());
384 return false;
385 }
386
387 try {
388 gpg_exitCode = p.exitValue();
389 } catch (IllegalThreadStateException itse) {
390 return false;
391 }
392 gpg_result = psr_stdout.getString();
393 gpg_err = psr_stderr.getString();
394
395 return true;
396 }
397
398 /**
399 * A utility method for creating a unique temporary file when needed by one
400 * of the main methods. <BR>
401 * The file handle is store in tmpFile object var.
402 *
403 * @param inStr
404 * data to write into the file.
405 * @return true if success
406 */
407 private File createTempFile(String inStr) {
408 File tmpFile = null;
409 FileWriter fw;
410
411 try {
412 tmpFile = File.createTempFile("YGnuPG", null);
413 } catch (Exception e) {
414 System.out.println("Cannot create temp file " + e.getMessage());
415 return null;
416 }
417
418 try {
419 fw = new FileWriter(tmpFile);
420 fw.write(inStr);
421 fw.flush();
422 fw.close();
423 } catch (Exception e) {
424 // delete our file:
425 tmpFile.delete();
426
427 System.out.println("Cannot write temp file " + e.getMessage());
428 return null;
429 }
430
431 return tmpFile;
432 }
433
434 /**
435 * Default constructor
436 */
437 public GnuPG() {
438 kGnuPGCommand = Settings.getInstance().getProperty("gpgApplication",
439 "gpg")
440 + " " + kGnuPGArgs;
441 }
442
443 public GnuPG(String command) {
444 kGnuPGCommand = command;
445 }
446
447 /**
448 * Description of the Method
449 *
450 * @param xEncryptedData
451 * Description of the Parameter
452 * @return Description of the Return Value
453 */
454 public String decryptExtension(String xEncryptedData) {
455 String gnupgPassword = BuddyList.getInstance().getGnuPGPassword();
456 String encoding = null;
457 xEncryptedData = xEncryptedData.replaceAll("(\n)+$", "");
458 xEncryptedData = xEncryptedData.replaceAll("^(\n)+", "");
459 if ((gnupgPassword != null)
460 && decrypt("-----BEGIN PGP MESSAGE-----\nVersion: bla\n\n"
461 + xEncryptedData + "\n-----END PGP MESSAGE-----\n",
462 gnupgPassword)) {
463 try {
464 String systemEncoding = new String(getResult().getBytes(),
465 "UTF8");
466 encoding = systemEncoding;
467 } catch (java.io.UnsupportedEncodingException e) {
468 }
469
470 }
471 return encoding.replaceAll("\n+$", "");
472 }
473
474 /**
475 * Description of the Method
476 *
477 * @param Data
478 * Description of the Parameter
479 * @param gnupgSecretKey
480 * Description of the Parameter
481 * @param gnupgPublicKey
482 * Description of the Parameter
483 * @return Description of the Return Value
484 */
485 public String encryptExtension(String Data, String gnupgSecretKey,
486 String gnupgPublicKey) {
487 String encryptedData = null;
488 try {
489 byte[] utf8 = Data.getBytes("UTF8");
490 String string = new String(utf8, MiscUtils.streamEncoding());
491 Data = string;
492 } catch (java.io.UnsupportedEncodingException e) {
493 }
494
495 if (encrypt(Data, gnupgSecretKey, gnupgPublicKey)) {
496 encryptedData = getResult();
497 encryptedData = encryptedData.replaceAll(
498 "-----BEGIN PGP MESSAGE-----(\n.*)+\n\n", "");
499 encryptedData = encryptedData.replaceAll(
500 "\n-----END PGP MESSAGE-----\n", "");
501
502 }
503 return encryptedData;
504 }
505
506 /**
507 * Description of the Method
508 *
509 * @param Data
510 * Description of the Parameter
511 * @param gnupgSecretKey
512 * Description of the Parameter
513 * @param gnupgPublicKey
514 * Description of the Parameter
515 * @return Description of the Return Value
516 */
517 public String signExtension(String Data, String gnupgSecretKey) {
518 String gnupgPassword = BuddyList.getInstance().getGnuPGPassword();
519 String signedData = null;
520 try {
521 byte[] utf8 = Data.getBytes("UTF8");
522 String string = new String(utf8, MiscUtils.streamEncoding());
523 Data = string;
524 } catch (java.io.UnsupportedEncodingException e) {
525 }
526
527 if ((gnupgPassword != null)
528 && (sign(Data, gnupgSecretKey, gnupgPassword))) {
529 signedData = getResult();
530 signedData = signedData.replaceAll(
531 "-----BEGIN PGP SIGNATURE-----(\n.*)+\n\n", "");
532 signedData = signedData.replaceAll(
533 "\n-----END PGP SIGNATURE-----\n", "");
534 signedData = signedData.replaceAll("^(\n)+", "");
535 signedData = signedData.replaceAll("(\n)+$", "");
536 }
537 return signedData;
538 }
539
540 public String verifyExtension(String xSignedData, String messageBody) {
541 String id = null;
542 try {
543 byte[] utf8 = messageBody.getBytes("UTF8");
544 String string = new String(utf8, MiscUtils.streamEncoding());
545 messageBody = string;
546 } catch (java.io.UnsupportedEncodingException e) {
547 }
548 messageBody = messageBody.replaceAll("(\n)+$", "");
549 xSignedData = xSignedData.replaceAll("(\n)+$", "");
550 messageBody = messageBody.replaceAll("^(\n)+", "");
551 xSignedData = xSignedData.replaceAll("^(\n)+", "");
552 if (verify("-----BEGIN PGP SIGNATURE-----\nVersion: bla\n\n"
553 + xSignedData + "\n-----END PGP SIGNATURE-----", messageBody)) {
554 id = getErrorString();
555 id = id.replaceAll(".*ID (.*)(\n.*)+", "$1");
556 }
557 return id;
558 }
559
560 }
561