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