You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

WalkEncryption.java 9.8KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274
  1. /*
  2. * Copyright (C) 2008, Shawn O. Pearce <spearce@spearce.org>
  3. * and other copyright owners as documented in the project's IP log.
  4. *
  5. * This program and the accompanying materials are made available
  6. * under the terms of the Eclipse Distribution License v1.0 which
  7. * accompanies this distribution, is reproduced below, and is
  8. * available at http://www.eclipse.org/org/documents/edl-v10.php
  9. *
  10. * All rights reserved.
  11. *
  12. * Redistribution and use in source and binary forms, with or
  13. * without modification, are permitted provided that the following
  14. * conditions are met:
  15. *
  16. * - Redistributions of source code must retain the above copyright
  17. * notice, this list of conditions and the following disclaimer.
  18. *
  19. * - Redistributions in binary form must reproduce the above
  20. * copyright notice, this list of conditions and the following
  21. * disclaimer in the documentation and/or other materials provided
  22. * with the distribution.
  23. *
  24. * - Neither the name of the Eclipse Foundation, Inc. nor the
  25. * names of its contributors may be used to endorse or promote
  26. * products derived from this software without specific prior
  27. * written permission.
  28. *
  29. * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
  30. * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
  31. * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
  32. * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
  33. * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
  34. * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
  35. * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
  36. * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
  37. * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
  38. * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
  39. * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
  40. * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
  41. * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  42. */
  43. package org.eclipse.jgit.transport;
  44. import java.io.IOException;
  45. import java.io.InputStream;
  46. import java.io.OutputStream;
  47. import java.net.HttpURLConnection;
  48. import java.security.GeneralSecurityException;
  49. import java.security.spec.AlgorithmParameterSpec;
  50. import java.text.MessageFormat;
  51. import javax.crypto.Cipher;
  52. import javax.crypto.CipherInputStream;
  53. import javax.crypto.CipherOutputStream;
  54. import javax.crypto.SecretKey;
  55. import javax.crypto.SecretKeyFactory;
  56. import javax.crypto.spec.IvParameterSpec;
  57. import javax.crypto.spec.PBEKeySpec;
  58. import javax.crypto.spec.PBEParameterSpec;
  59. import org.eclipse.jgit.internal.JGitText;
  60. abstract class WalkEncryption {
  61. static final WalkEncryption NONE = new NoEncryption();
  62. static final String JETS3T_CRYPTO_VER = "jets3t-crypto-ver"; //$NON-NLS-1$
  63. static final String JETS3T_CRYPTO_ALG = "jets3t-crypto-alg"; //$NON-NLS-1$
  64. abstract OutputStream encrypt(OutputStream os) throws IOException;
  65. abstract InputStream decrypt(InputStream in) throws IOException;
  66. abstract void request(HttpURLConnection u, String prefix);
  67. abstract void validate(HttpURLConnection u, String prefix) throws IOException;
  68. // TODO mixed ciphers
  69. // consider permitting mixed ciphers to facilitate algorithm migration
  70. // i.e. user keeps the password, but changes the algorithm
  71. // then existing remote entries will still be readable
  72. protected void validateImpl(final HttpURLConnection u, final String prefix,
  73. final String version, final String name) throws IOException {
  74. String v;
  75. v = u.getHeaderField(prefix + JETS3T_CRYPTO_VER);
  76. if (v == null)
  77. v = ""; //$NON-NLS-1$
  78. if (!version.equals(v))
  79. throw new IOException(MessageFormat.format(JGitText.get().unsupportedEncryptionVersion, v));
  80. v = u.getHeaderField(prefix + JETS3T_CRYPTO_ALG);
  81. if (v == null)
  82. v = ""; //$NON-NLS-1$
  83. // Standard names are not case-sensitive.
  84. // http://docs.oracle.com/javase/8/docs/technotes/guides/security/StandardNames.html
  85. if (!name.equalsIgnoreCase(v))
  86. throw new IOException(MessageFormat.format(JGitText.get().unsupportedEncryptionAlgorithm, v));
  87. }
  88. IOException error(final Throwable why) {
  89. final IOException e;
  90. e = new IOException(MessageFormat.format(JGitText.get().encryptionError, why.getMessage()));
  91. e.initCause(why);
  92. return e;
  93. }
  94. private static class NoEncryption extends WalkEncryption {
  95. @Override
  96. void request(HttpURLConnection u, String prefix) {
  97. // Don't store any request properties.
  98. }
  99. @Override
  100. void validate(final HttpURLConnection u, final String prefix)
  101. throws IOException {
  102. validateImpl(u, prefix, "", ""); //$NON-NLS-1$ //$NON-NLS-2$
  103. }
  104. @Override
  105. InputStream decrypt(InputStream in) {
  106. return in;
  107. }
  108. @Override
  109. OutputStream encrypt(OutputStream os) {
  110. return os;
  111. }
  112. }
  113. // PBEParameterSpec factory for Java (version <= 7).
  114. // Does not support AlgorithmParameterSpec.
  115. static PBEParameterSpec java7PBEParameterSpec(byte[] salt,
  116. int iterationCount) {
  117. return new PBEParameterSpec(salt, iterationCount);
  118. }
  119. // PBEParameterSpec factory for Java (version >= 8).
  120. // Adds support for AlgorithmParameterSpec.
  121. static PBEParameterSpec java8PBEParameterSpec(byte[] salt,
  122. int iterationCount, AlgorithmParameterSpec paramSpec) {
  123. try {
  124. @SuppressWarnings("boxing")
  125. PBEParameterSpec instance = PBEParameterSpec.class
  126. .getConstructor(byte[].class, int.class,
  127. AlgorithmParameterSpec.class)
  128. .newInstance(salt, iterationCount, paramSpec);
  129. return instance;
  130. } catch (Exception e) {
  131. throw new RuntimeException(e);
  132. }
  133. }
  134. // Current runtime version.
  135. // https://docs.oracle.com/javase/7/docs/technotes/guides/versioning/spec/versioning2.html
  136. static double javaVersion() {
  137. return Double.parseDouble(System.getProperty("java.specification.version")); //$NON-NLS-1$
  138. }
  139. /**
  140. * JetS3t compatibility reference: <a href=
  141. * "https://bitbucket.org/jmurty/jets3t/src/156c00eb160598c2e9937fd6873f00d3190e28ca/src/org/jets3t/service/security/EncryptionUtil.java">
  142. * EncryptionUtil.java</a>
  143. * <p>
  144. * Note: EncryptionUtil is inadequate:
  145. * <li>EncryptionUtil.isCipherAvailableForUse checks encryption only which
  146. * "always works", but in JetS3t both encryption and decryption use non-IV
  147. * aware algorithm parameters for all PBE specs, which breaks in case of AES
  148. * <li>that means that only non-IV algorithms will work round trip in
  149. * JetS3t, such as PBEWithMD5AndDES and PBEWithSHAAndTwofish-CBC
  150. * <li>any AES based algorithms such as "PBE...With...And...AES" will not
  151. * work, since they need proper IV setup
  152. */
  153. static class ObjectEncryptionJetS3tV2 extends WalkEncryption {
  154. static final String JETS3T_VERSION = "2"; //$NON-NLS-1$
  155. static final String JETS3T_ALGORITHM = "PBEWithMD5AndDES"; //$NON-NLS-1$
  156. static final int JETS3T_ITERATIONS = 5000;
  157. static final int JETS3T_KEY_SIZE = 32;
  158. static final byte[] JETS3T_SALT = { //
  159. (byte) 0xA4, (byte) 0x0B, (byte) 0xC8, (byte) 0x34, //
  160. (byte) 0xD6, (byte) 0x95, (byte) 0xF3, (byte) 0x13 //
  161. };
  162. // Size 16, see com.sun.crypto.provider.AESConstants.AES_BLOCK_SIZE
  163. static final byte[] ZERO_AES_IV = new byte[16];
  164. private final String cryptoVer = JETS3T_VERSION;
  165. private final String cryptoAlg;
  166. private final SecretKey secretKey;
  167. private final AlgorithmParameterSpec paramSpec;
  168. ObjectEncryptionJetS3tV2(final String algo, final String key)
  169. throws GeneralSecurityException {
  170. cryptoAlg = algo;
  171. // Standard names are not case-sensitive.
  172. // http://docs.oracle.com/javase/8/docs/technotes/guides/security/StandardNames.html
  173. String cryptoName = cryptoAlg.toUpperCase();
  174. if (!cryptoName.startsWith("PBE")) //$NON-NLS-1$
  175. throw new GeneralSecurityException(JGitText.get().encryptionOnlyPBE);
  176. PBEKeySpec keySpec = new PBEKeySpec(key.toCharArray(), JETS3T_SALT, JETS3T_ITERATIONS, JETS3T_KEY_SIZE);
  177. secretKey = SecretKeyFactory.getInstance(algo).generateSecret(keySpec);
  178. // Detect algorithms which require initialization vector.
  179. boolean useIV = cryptoName.contains("AES"); //$NON-NLS-1$
  180. // PBEParameterSpec algorithm parameters are supported from Java 8.
  181. boolean isJava8 = javaVersion() >= 1.8;
  182. if (useIV && isJava8) {
  183. // Support IV where possible:
  184. // * since JCE provider uses random IV for PBE/AES
  185. // * and there is no place to store dynamic IV in JetS3t V2
  186. // * we use static IV, and tolerate increased security risk
  187. // TODO back port this change to JetS3t V2
  188. // See:
  189. // https://bitbucket.org/jmurty/jets3t/raw/156c00eb160598c2e9937fd6873f00d3190e28ca/src/org/jets3t/service/security/EncryptionUtil.java
  190. // http://cr.openjdk.java.net/~mullan/webrevs/ascarpin/webrev.00/raw_files/new/src/share/classes/com/sun/crypto/provider/PBES2Core.java
  191. IvParameterSpec paramIV = new IvParameterSpec(ZERO_AES_IV);
  192. paramSpec = java8PBEParameterSpec(JETS3T_SALT, JETS3T_ITERATIONS, paramIV);
  193. } else {
  194. // Strict legacy JetS3t V2 compatibility, with no IV support.
  195. paramSpec = java7PBEParameterSpec(JETS3T_SALT, JETS3T_ITERATIONS);
  196. }
  197. }
  198. @Override
  199. void request(final HttpURLConnection u, final String prefix) {
  200. u.setRequestProperty(prefix + JETS3T_CRYPTO_VER, cryptoVer);
  201. u.setRequestProperty(prefix + JETS3T_CRYPTO_ALG, cryptoAlg);
  202. }
  203. @Override
  204. void validate(final HttpURLConnection u, final String prefix)
  205. throws IOException {
  206. validateImpl(u, prefix, cryptoVer, cryptoAlg);
  207. }
  208. @Override
  209. OutputStream encrypt(final OutputStream os) throws IOException {
  210. try {
  211. final Cipher cipher = Cipher.getInstance(cryptoAlg);
  212. cipher.init(Cipher.ENCRYPT_MODE, secretKey, paramSpec);
  213. return new CipherOutputStream(os, cipher);
  214. } catch (GeneralSecurityException e) {
  215. throw error(e);
  216. }
  217. }
  218. @Override
  219. InputStream decrypt(final InputStream in) throws IOException {
  220. try {
  221. final Cipher cipher = Cipher.getInstance(cryptoAlg);
  222. cipher.init(Cipher.DECRYPT_MODE, secretKey, paramSpec);
  223. return new CipherInputStream(in, cipher);
  224. } catch (GeneralSecurityException e) {
  225. throw error(e);
  226. }
  227. }
  228. }
  229. }