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