PageRenderTime 107ms CodeModel.GetById 92ms app.highlight 10ms RepoModel.GetById 0ms app.codeStats 1ms

/bundles/plugins-trunk/WincryptCipher/src/wincrypt/WincryptCipher.java

#
Java | 472 lines | 182 code | 26 blank | 264 comment | 36 complexity | 91316e630623c1637186f231f4699a2f MD5 | raw file
  1/*
  2 * WincryptCipherPlugin - A jEdit plugin as wincrypt cipher implementation for the CipherPlugin
  3 * :tabSize=4:indentSize=4:noTabs=true:
  4 *
  5 * Copyright (C) 2007 Björn "Vampire" Kautler
  6 *
  7 * This program is free software; you can redistribute it and/or
  8 * modify it under the terms of the GNU General Public License
  9 * as published by the Free Software Foundation; either version 2
 10 * of the License, or (at your option) any later version.
 11 *
 12 * This program is distributed in the hope that it will be useful,
 13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 15 * GNU General Public License for more details.
 16 *
 17 * You should have received a copy of the GNU General Public License
 18 * along with this program; if not, write to the Free Software
 19 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
 20 */
 21
 22package wincrypt;
 23
 24import java.io.File;
 25import java.io.UnsupportedEncodingException;
 26
 27import cipher.AdditionalInformationRequester;
 28import cipher.Cipher;
 29
 30import edu.umd.cs.findbugs.annotations.CheckForNull;
 31import edu.umd.cs.findbugs.annotations.CheckReturnValue;
 32import edu.umd.cs.findbugs.annotations.NonNull;
 33import edu.umd.cs.findbugs.annotations.Nullable;
 34import edu.umd.cs.findbugs.annotations.SuppressWarnings;
 35
 36import net.jcip.annotations.GuardedBy;
 37import net.jcip.annotations.ThreadSafe;
 38
 39import org.apache.commons.codec.binary.Base64;
 40
 41import org.gjt.sp.jedit.jEdit;
 42import org.gjt.sp.jedit.MiscUtilities;
 43import org.gjt.sp.jedit.OperatingSystem;
 44import org.gjt.sp.jedit.PluginJAR;
 45
 46/**
 47 * <p>A wincrypt cipher implementation for the CipherPlugin.</p>
 48 * 
 49 * <p>The jEdit ServicesAPI name of this implementation is &quot;wincrypt&quot;.</p>
 50 * 
 51 * <p>This plugin uses the Windows CryptoAPI to encrypt data. It uses the
 52 * Windows user login credentials to encrypt data and so decryption can
 53 * only happen by the same logged in user, in most cases also only on the
 54 * same machine. This API works on {@code byte} arrays. Because of that, the
 55 * {@link #encryptToString()} method transforms the result with Base64
 56 * into a {@code String} and the {@link #setEncryptedData(String)}
 57 * method expects Base64 encoded data.</p>
 58 * 
 59 * <p>The additional information this implementation expects is
 60 * either exactly one {@code String}
 61 * object describing the data to be encrypted or decrypted, or
 62 * none in which case the description is assumed an empty string.
 63 * The description gets encrypted with the data
 64 * and on decryption, it is used to verify the process.</p>
 65 * 
 66 * <p>This class is thread-safe, the lock for the mutable state is this instance.
 67 * To ensure the atomicity of the encrypting or decrypting
 68 * process in a thread safe manner, the subsequent calls to
 69 * <ul><li>one of the {@code set*Data()},</li>
 70 * <li>one of the {@code setEntropy()},</li>
 71 * <li>the {@code setAdditionalInformation()} and</li>
 72 * <li>one of the {@code *cryptTo*()} methods</li></ul>
 73 * should all be made from within one {@code synchronized} block,
 74 * locking on this instance.</p>
 75 * 
 76 * @author Björn "Vampire" Kautler
 77 * @since WincryptCipherPlugin 0.1
 78 * @see cipher.Cipher
 79 * @see <a href="http://msdn2.microsoft.com/en-us/library/aa380261.aspx">CryptProtectData</a>
 80 * @see <a href="http://msdn2.microsoft.com/en-us/library/aa380882.aspx">CryptUnprotectData</a>
 81 */
 82@SuppressWarnings(value = "DM_GC",
 83                  justification = "Garbage Collection is needed to eventually free the DLL if it is blocked by a \"dead\" ClassLoaded")
 84@ThreadSafe
 85public class WincryptCipher implements Cipher {
 86    @GuardedBy("this") private byte[] rawData;
 87    @GuardedBy("this") private byte[] encryptedData;
 88    @GuardedBy("this") private byte[] entropy;
 89    @GuardedBy("this") private char[] description;
 90    
 91    static {
 92        if (OperatingSystem.isWindows()) {
 93            PluginJAR wincryptCipherPluginJAR = jEdit.getPlugin("wincrypt.WincryptCipherPlugin").getPluginJAR();
 94            String dllPath = MiscUtilities.constructPath(MiscUtilities.getParentOfPath(wincryptCipherPluginJAR.getPath()),"WincryptCipher.dll");
 95            if (!new File(dllPath).exists()) {
 96                jEdit.removePluginJAR(wincryptCipherPluginJAR,false);
 97                jEdit.addPluginJAR(wincryptCipherPluginJAR.getPath());
 98                throw new UnsatisfiedLinkError("Missing WincryptCipher.dll");
 99            }
100            boolean loaded = false;
101            int tries = 0;
102            UnsatisfiedLinkError ule = null;
103            while (!loaded && (tries < 5)) {
104                tries++;
105                try {
106                    System.load(dllPath);
107                    loaded = true;
108                } catch (UnsatisfiedLinkError innnerUle) {
109                    ule = innnerUle;
110                    System.gc();
111                }
112            }
113            if (!loaded) {
114                throw ule;
115            }
116        }
117    }
118    
119    /**
120     * Constructs a new {@code WincryptCipher}.
121     */
122    public WincryptCipher() {
123        rawData = new byte[0];
124        encryptedData = new byte[0];
125        entropy = new byte[0];
126        description = new char[0];
127    }
128    
129    /**
130     * The actual encryption function which uses the Windows
131     * CryptoAPI to encrypt the given data.
132     * 
133     * @param rawData     The raw data to encrypt
134     * @param description The description used to verify decryption
135     * @param entropy     The additional entropy like a password for example
136     * @return The encrypted data as {@code byte} array or {@code null} if encryption was not successful
137     * @see #decryptNative(byte[], char[], byte[])
138     */
139    @CheckForNull
140    @CheckReturnValue(explanation = "If the encryption result is not of further interest, the process should not be invoked")
141    private native byte[] encryptNative(@NonNull byte[] rawData, @NonNull char[] description, @NonNull byte[] entropy);
142    
143    /**
144     * The actual decryption function which uses the Windows
145     * CryptoAPI to decrypt the given data.
146     * 
147     * @param encryptedData The encrypted data to decrypt
148     * @param description   The description used to encrypt the data to verify the data integrity
149     * @param entropy       The additional entropy like a password for example
150     * @return The decrpyted data as {@code byte} array or {@code null} if decryption was not successful
151     * @see #encryptNative(byte[], char[], byte[])
152     */
153    @CheckForNull
154    @CheckReturnValue(explanation = "If the decryption result is not of further interest, the process should not be invoked")
155    private native byte[] decryptNative(@NonNull byte[] encryptedData, @NonNull char[] description, @NonNull byte[] entropy);
156    
157    /**
158     * Checks if the Windows CryptoAPI is available.
159     * 
160     * @return Whether the Windows CryptoAPI is available or not.
161     */
162    @CheckReturnValue(explanation = "If the availability got requested it should be used")
163    private native boolean isNativeAvailable();
164    
165    /**
166     * <p>Sets the raw data for an encrypting process
167     * from a {@code byte} array. If your raw data is a password you may
168     * consider using {@link #setRawData(String)}.</p>
169     * 
170     * <p>If {@code rawData} is {@code null}, {@code rawData} is
171     * set to an empty {@code byte} array.</p>
172     * 
173     * <p>The method is synchronized on this instance.</p>
174     * 
175     * @param rawData The raw data as a {@code byte} array
176     * @see #setRawData(String)
177     * @see cipher.Cipher#setRawData(byte[])
178     */
179    public synchronized void setRawData(@Nullable byte[] rawData) {
180        if (null == rawData) {
181            this.rawData = new byte[0];
182        } else {
183            this.rawData = new byte[rawData.length];
184            System.arraycopy(rawData,0,this.rawData,0,rawData.length);
185        }
186    }
187    
188    /**
189     * <p>Sets the raw data for an encrypting process
190     * from a {@code String} object.</p>
191     * 
192     * <p>If {@code rawData} is {@code null}, {@code rawData} is
193     * set to an empty {@code byte} array, otherwise
194     * {@code rawData.getBytes("UTF-8")} is used to
195     * transform the parameter to a {@code byte} array.</p>
196     * 
197     * <p>The method is synchronized on this instance.</p>
198     * 
199     * @param rawData The raw data as a {@code String} object
200     * @see #setRawData(byte[])
201     * @see cipher.Cipher#setRawData(java.lang.String)
202     */
203    public synchronized void setRawData(@Nullable String rawData) {
204        if (null == rawData) {
205            this.rawData = new byte[0];
206        } else {
207            try {
208                this.rawData = rawData.getBytes("UTF-8");
209            } catch (UnsupportedEncodingException uee) {
210                InternalError ie = new InternalError("JVM doesn't support UTF-8");
211                ie.initCause(uee);
212                throw ie;
213            }
214        }
215    }
216    
217    /**
218     * <p>Sets the encrypted data for a decrypting process
219     * from a {@code byte} array.</p>
220     * 
221     * <p>If {@code encryptedData} is {@code null},
222     * {@code encryptedData} is set to an empty
223     * {@code byte} array.</p>
224     * 
225     * <p>The method is synchronized on this instance.</p>
226     * 
227     * @param encryptedData The encrypted data as a {@code byte} array
228     * @see #setEncryptedData(String)
229     * @see cipher.Cipher#setEncryptedData(byte[])
230     */
231    public synchronized void setEncryptedData(@Nullable byte[] encryptedData) {
232        if (null == encryptedData) {
233            this.encryptedData = new byte[0];
234        } else {
235            this.encryptedData = new byte[encryptedData.length];
236            System.arraycopy(encryptedData,0,this.encryptedData,0,encryptedData.length);
237        }
238    }
239    
240    /**
241     * <p>Sets the encrypted data for a decrypting process
242     * from a {@code String} object. Encrypted data in String
243     * form is supposed to be Base64 encoded by this implementation.</p>
244     * 
245     * <p>If {@code encryptedData} is {@code null},
246     * {@code encryptedData} is set to an empty
247     * {@code byte} array, otherwise the {@code String}
248     * gets Base64 decoded to transform the parameter
249     * to a {@code byte} array.</p>
250     * 
251     * <p>The method is synchronized on this instance.</p>
252     * 
253     * @param encryptedData The encrypted data as a {@code String} object
254     * @see #setEncryptedData(byte[])
255     * @see cipher.Cipher#setEncryptedData(java.lang.String)
256     */
257    public synchronized void setEncryptedData(@Nullable String encryptedData) {
258        if (null == encryptedData) {
259            this.encryptedData = new byte[0];
260        } else {
261            this.encryptedData = Base64.decodeBase64(encryptedData.getBytes());
262        }
263    }
264    
265    /**
266     * <p>Sets the entropy for an encrypting or decrypting process
267     * from a {@code byte} array. The entropy could e. g. be a
268     * &quot;fingerprint&quot; in byte data.</p>
269     * 
270     * <p>If {@code entropy} is {@code null}, {@code entropy}
271     * is set to an empty {@code byte} array.</p>
272     * 
273     * <p>The method is synchronized on this instance.</p>
274     * 
275     * @param entropy The entropy as a {@code byte} array
276     * @see #setEntropy(String)
277     * @see cipher.Cipher#setEntropy(byte[])
278     */
279    public synchronized void setEntropy(@Nullable byte[] entropy) {
280        if (null == entropy) {
281            this.entropy = new byte[0];
282        } else {
283            this.entropy = new byte[entropy.length];
284            System.arraycopy(entropy,0,this.entropy,0,entropy.length);
285        }
286    }
287    
288    /**
289     * <p>Sets the entropy for an encrypting or decrypting process
290     * from a {@code String} object. The entropy could e. g. be a
291     * password, like a master password that is used to encrypt
292     * all stored passwords.</p>
293     * 
294     * <p>If {@code entropy} is {@code null}, {@code entropy} is
295     * set to an empty {@code byte} array, otherwise
296     * {@code entropy.getBytes("UTF-8")} is used to
297     * transform the parameter to a {@code byte} array.</p>
298     * 
299     * <p>The method is synchronized on this instance.</p>
300     * 
301     * @param entropy The entropy as a {@code String} object
302     * @see #setEntropy(byte[])
303     * @see cipher.Cipher#setEntropy(java.lang.String)
304     */
305    public synchronized void setEntropy(@Nullable String entropy) {
306        if (null == entropy) {
307            this.entropy = new byte[0];
308        } else {
309            try {
310                entropy.getBytes("UTF-8");
311            } catch (UnsupportedEncodingException uee) {
312                InternalError ie = new InternalError("JVM doesn't support UTF-8");
313                ie.initCause(uee);
314                throw ie;
315            }
316        }
317    }
318    
319    /**
320     * <p>Returns an {@code AdditionalInformationRequester} that request
321     * the needed additional information for an encrypting or
322     * decrypting process. If no additional information is needed,
323     * the method should return {@code null}.</p>
324     * 
325     * @return The additional information requester
326     * @see cipher.Cipher#getAdditionalInformationRequester()
327     */
328    @CheckForNull
329    @CheckReturnValue(explanation = "If the additional information requester got requested it should be used")
330    public AdditionalInformationRequester getAdditionalInformationRequester() {
331        return new WincryptCipherAdditionalInformationRequester();
332    }
333    
334    /**
335     * <p>Sets the additional information for an encrypting or
336     * decrypting process from an {@code Object} vararg.</p>
337     * 
338     * <p>This implementation expects either exactly one {@code String}
339     * object describing the data to be encrypted or decrypted, or
340     * none in which case the description is assumed an empty string.
341     * The description gets encrypted with the data and on
342     * decryption, it is used to verify the process.</p>
343     * 
344     * <p>The method is synchronized on this instance.</p>
345     * 
346     * @param additionalInformation The description for the data
347     * @throws IllegalArgumentException If there is not exactly zero or one {@code String} object given
348     * @see cipher.Cipher#setAdditionalInformation(java.lang.Object[])
349     */
350    public synchronized void setAdditionalInformation(@Nullable Object... additionalInformation) {
351        if ((null == additionalInformation) ||
352            (0 == additionalInformation.length)){
353            description = new char[0];
354        } else if ((1 != additionalInformation.length) ||
355                   !(additionalInformation[0] instanceof String)) {
356            throw new IllegalArgumentException("additionalInformation has to be exactly one String object");
357        } else {
358            description = ((String)additionalInformation[0]).toCharArray();
359        }
360    }
361    
362    /**
363     * <p>Encrypts the given raw data with the given entropy
364     * and the given additional information. If you want
365     * to store the encrypted data on some text device like
366     * a text file, consider using {@link #encryptToString()}.</p>
367     * 
368     * <p>The method is synchronized on this instance.</p>
369     * 
370     * @return The encrypted data as a {@code byte} array or {@code null} if encryption was not successful
371     * @see #encryptToString()
372     * @see cipher.Cipher#encryptToByteArray()
373     */
374    @CheckForNull
375    @CheckReturnValue(explanation = "If the encryption result is not of further interest, the process should not be invoked")
376    public synchronized byte[] encryptToByteArray() {
377        return encryptNative(rawData,description,entropy);
378    }
379    
380    /**
381     * <p>Decrypts the given encrypted data with the given
382     * entropy and the given additional information.
383     * If you want to decrypt a password, consider using
384     * {@link #decryptToString()}.</p>
385     * 
386     * <p>The method is synchronized on this instance.</p>
387     * 
388     * @return The decrypted data as a {@code byte} array or {@code null} if decryption was not successful
389     * @see #decryptToString()
390     * @see cipher.Cipher#decryptToByteArray()
391     */
392    @CheckForNull
393    @CheckReturnValue(explanation = "If the decryption result is not of further interest, the process should not be invoked")
394    public synchronized byte[] decryptToByteArray() {
395        return decryptNative(encryptedData,description,entropy);
396    }
397    
398    /**
399     * <p>Encrypts the given raw data with the given entropy
400     * and the given additional information. The returned
401     * {@code String} object is a Base64 encoded representation
402     * of the encryptedData.</p>
403     * 
404     * <p>The method is not synchronized but forwards the work
405     * to the {@code encryptToByteArray()} method
406     * which is synchronized on this instance.</p>
407     * 
408     * @return The encrypted data as a Base64 encoded {@code String} object or {@code null} if encryption was not successful
409     * @see #encryptToByteArray()
410     * @see cipher.Cipher#encryptToString()
411     */
412    @CheckForNull
413    @CheckReturnValue(explanation = "If the encryption result is not of further interest, the process should not be invoked")
414    public String encryptToString() {
415        byte[] encryptedData = encryptToByteArray();
416        if (null == encryptedData) {
417            return null;
418        }
419        return new String(Base64.encodeBase64(encryptedData));
420    }
421    
422    /**
423     * <p>Decrypts the given encrypted data with the given
424     * entropy and the given additional information.
425     * The decrypted data is transformed to a {@code String}
426     * by using the method {@code new String(decryptedData,"UTF-8")}.</p>
427     * 
428     * <p>The method is not synchronized but forwards the work
429     * to the {@code encryptToByteArray()} method
430     * which is synchronized on this instance.</p>
431     * 
432     * @return The decrypted data as a {@code String} object or {@code null} if decryption was not successful
433     * @see #decryptToByteArray()
434     * @see cipher.Cipher#decryptToString()
435     */
436    @CheckForNull
437    @CheckReturnValue(explanation = "If the decryption result is not of further interest, the process should not be invoked")
438    public String decryptToString() {
439        byte[] decryptedData = decryptToByteArray();
440        if (null == decryptedData) {
441            return null;
442        }
443        try {
444            return new String(decryptedData,"UTF-8");
445        } catch (UnsupportedEncodingException uee) {
446            InternalError ie = new InternalError("JVM doesn't support UTF-8");
447            ie.initCause(uee);
448            throw ie;
449        }
450    }
451    
452    /**
453     * <p>Checks if this {@code Cipher} implementation is
454     * currently available. This method should return
455     * the same value during one JVM session.</p>
456     * 
457     * <p>This implementation is unavailable if the operating
458     * system is not Windows or if the Windows CryptoAPI
459     * is not available.</p>
460     * 
461     * @return Whether the implementation is currently available
462     * @see cipher.Cipher#isAvailable()
463     */
464    @CheckReturnValue(explanation = "If the availability got requested it should be used")
465    public boolean isAvailable() {
466        if (OperatingSystem.isWindows()) {
467            return isNativeAvailable();
468        } else {
469            return false;
470        }
471    }
472}