PageRenderTime 2911ms CodeModel.GetById 22ms RepoModel.GetById 6ms app.codeStats 1ms

/projects/jruby-1.7.3/src/org/jruby/ext/openssl/PKeyDH.java

https://gitlab.com/essere.lab.public/qualitas.class-corpus
Java | 398 lines | 281 code | 51 blank | 66 comment | 47 complexity | 93294a1d988274c1ba9ff5bc2a25a7e8 MD5 | raw file
  1. /***** BEGIN LICENSE BLOCK *****
  2. * Version: EPL 1.0/GPL 2.0/LGPL 2.1
  3. *
  4. * The contents of this file are subject to the Common Public
  5. * License Version 1.0 (the "License"); you may not use this file
  6. * except in compliance with the License. You may obtain a copy of
  7. * the License at http://www.eclipse.org/legal/cpl-v10.html
  8. *
  9. * Software distributed under the License is distributed on an "AS
  10. * IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
  11. * implied. See the License for the specific language governing
  12. * rights and limitations under the License.
  13. *
  14. * Copyright (C) 2007 William N Dortch <bill.dortch@gmail.com>
  15. *
  16. * Alternatively, the contents of this file may be used under the terms of
  17. * either of the GNU General Public License Version 2 or later (the "GPL"),
  18. * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
  19. * in which case the provisions of the GPL or the LGPL are applicable instead
  20. * of those above. If you wish to allow use of your version of this file only
  21. * under the terms of either the GPL or the LGPL, and not to allow others to
  22. * use your version of this file under the terms of the EPL, indicate your
  23. * decision by deleting the provisions above and replace them with the notice
  24. * and other provisions required by the GPL or the LGPL. If you do not delete
  25. * the provisions above, a recipient may use your version of this file under
  26. * the terms of any one of the EPL, the GPL or the LGPL.
  27. ***** END LICENSE BLOCK *****/
  28. package org.jruby.ext.openssl;
  29. import java.io.IOException;
  30. import java.io.StringReader;
  31. import java.io.StringWriter;
  32. import java.math.BigInteger;
  33. import java.security.SecureRandom;
  34. import java.security.spec.InvalidParameterSpecException;
  35. import java.util.HashMap;
  36. import javax.crypto.spec.DHParameterSpec;
  37. import org.jruby.Ruby;
  38. import org.jruby.RubyClass;
  39. import org.jruby.RubyHash;
  40. import org.jruby.RubyModule;
  41. import org.jruby.RubyNumeric;
  42. import org.jruby.RubyString;
  43. import org.jruby.anno.JRubyMethod;
  44. import org.jruby.exceptions.RaiseException;
  45. import org.jruby.ext.openssl.x509store.PEMInputOutput;
  46. import org.jruby.runtime.Arity;
  47. import org.jruby.runtime.ObjectAllocator;
  48. import org.jruby.runtime.builtin.IRubyObject;
  49. import org.jruby.util.ByteList;
  50. /**
  51. * OpenSSL::PKey::DH implementation.
  52. *
  53. * @author <a href="mailto:bill.dortch@gmail.com">Bill Dortch</a>
  54. */
  55. public class PKeyDH extends PKey {
  56. private static final long serialVersionUID = 293266329939132250L;
  57. // parameters used in generating 'p'; see [ossl]/crypto/dh/dh_gen.c #dh_builtin_genparams
  58. private static final BigInteger GEN_2_ADD_PARAM = BigInteger.valueOf(24);
  59. private static final BigInteger GEN_2_REM_PARAM = BigInteger.valueOf(11);
  60. private static final BigInteger GEN_5_ADD_PARAM = BigInteger.valueOf(10);
  61. private static final BigInteger GEN_5_REM_PARAM = BigInteger.valueOf(3);
  62. private static final BigInteger DEFAULT_ADD_PARAM = BigInteger.valueOf(2);
  63. private static final BigInteger DEFAULT_REM_PARAM = BigInteger.ONE;
  64. private static final BigInteger TWO = BigInteger.valueOf(2);
  65. // from [ossl]/crypto/dh/dh.h
  66. private static final int OPENSSL_DH_MAX_MODULUS_BITS = 10000;
  67. private static ObjectAllocator PKEYDH_ALLOCATOR = new ObjectAllocator() {
  68. public IRubyObject allocate(Ruby runtime, RubyClass klass) {
  69. return new PKeyDH(runtime, klass);
  70. }
  71. };
  72. public static void createPKeyDH(Ruby runtime, RubyModule pkeyModule, RubyClass pkeyClass) {
  73. RubyClass dh = pkeyModule.defineClassUnder("DH", pkeyClass, PKEYDH_ALLOCATOR);
  74. RubyClass pkeyError = pkeyModule.getClass("PKeyError");
  75. pkeyModule.defineClassUnder("DHError",pkeyError,pkeyError.getAllocator());
  76. dh.defineAnnotatedMethods(PKeyDH.class);
  77. }
  78. public static RaiseException newDHError(Ruby runtime, String message) {
  79. return Utils.newError(runtime, "OpenSSL::PKey::DHError", message);
  80. }
  81. private static SecureRandom _secureRandom;
  82. private static SecureRandom getSecureRandom() {
  83. SecureRandom rand;
  84. if ((rand = _secureRandom) != null) {
  85. return rand;
  86. }
  87. // FIXME: do we want a particular algorithm / provider? BC?
  88. return _secureRandom = new SecureRandom();
  89. }
  90. // transient because: we do not want these value serialized (insecure)
  91. // volatile because: permits unsynchronized reads in some cases
  92. private transient volatile BigInteger dh_p;
  93. private transient volatile BigInteger dh_g;
  94. private transient volatile BigInteger dh_pub_key;
  95. private transient volatile BigInteger dh_priv_key;
  96. // FIXME! need to figure out what it means in MRI/OSSL code to
  97. // claim a DH is(/has) private if an engine is present -- doesn't really
  98. // map to Java implementation.
  99. //private volatile boolean haveEngine;
  100. public PKeyDH(Ruby runtime, RubyClass clazz) {
  101. super(runtime, clazz);
  102. }
  103. @JRubyMethod(name="initialize", rest=true)
  104. public synchronized IRubyObject dh_initialize(IRubyObject[] args) {
  105. Ruby runtime = getRuntime();
  106. if (this.dh_p != null || this.dh_g != null || this.dh_pub_key != null || this.dh_priv_key != null) {
  107. throw newDHError(runtime, "illegal initialization");
  108. }
  109. int argc = Arity.checkArgumentCount(runtime, args, 0, 2);
  110. if (argc > 0) {
  111. IRubyObject arg0 = args[0];
  112. if (argc == 1 && arg0 instanceof RubyString) {
  113. try {
  114. DHParameterSpec spec = PEMInputOutput.readDHParameters(new StringReader(arg0.toString()));
  115. if (spec == null) {
  116. spec = org.jruby.ext.openssl.impl.PKey.readDHParameter(arg0.asString().getByteList().bytes());
  117. }
  118. if (spec == null) {
  119. throw runtime.newArgumentError("invalid DH PARAMETERS");
  120. }
  121. this.dh_p = spec.getP();
  122. this.dh_g = spec.getG();
  123. } catch (NoClassDefFoundError ncdfe) {
  124. throw newDHError(runtime, OpenSSLReal.bcExceptionMessage(ncdfe));
  125. } catch (IOException e) {
  126. throw runtime.newIOErrorFromException(e);
  127. }
  128. } else {
  129. int bits = RubyNumeric.fix2int(arg0);
  130. // g defaults to 2
  131. int gval = argc == 2 ? RubyNumeric.fix2int(args[1]) : 2;
  132. BigInteger p;
  133. try {
  134. p = generateP(bits, gval);
  135. } catch(IllegalArgumentException e) {
  136. throw runtime.newArgumentError(e.getMessage());
  137. }
  138. BigInteger g = BigInteger.valueOf(gval);
  139. BigInteger x = generateX(p);
  140. BigInteger y = generateY(p, g, x);
  141. this.dh_p = p;
  142. this.dh_g = g;
  143. this.dh_priv_key = x;
  144. this.dh_pub_key = y;
  145. }
  146. }
  147. return this;
  148. }
  149. public static BigInteger generateP(int bits, int g) {
  150. // FIXME? I'm following algorithms used in OpenSSL, could use JCE provider instead.
  151. // (Note that I tried that, but got mystifying values of g returned by the param generator.
  152. // In any case, in OpenSSL/MRI-OpenSSL, the caller supplies g, or it defaults to 2.)
  153. // see [ossl]/crypto/dh/dh_gen.c #dh_builtin_genparams
  154. if (bits < 2) throw new IllegalArgumentException("invalid bit length");
  155. if (g < 2) throw new IllegalArgumentException("invalid generator");
  156. // generate safe prime meeting appropriate add/rem (mod) criteria
  157. switch(g) {
  158. case 2:
  159. // add = 24, rem = 11
  160. return BN.generatePrime(bits, true, GEN_2_ADD_PARAM, GEN_2_REM_PARAM);
  161. case 5:
  162. // add = 10, rem = 3
  163. return BN.generatePrime(bits, true, GEN_5_ADD_PARAM, GEN_5_REM_PARAM);
  164. default:
  165. // add = 2, rem = 1
  166. return BN.generatePrime(bits, true, DEFAULT_ADD_PARAM, DEFAULT_REM_PARAM);
  167. }
  168. }
  169. public static BigInteger generateX(BigInteger p, int limit) {
  170. if (limit < 0) throw new IllegalArgumentException("invalid limit");
  171. BigInteger x;
  172. SecureRandom secureRandom = getSecureRandom();
  173. // adapting algorithm from org.bouncycastle.crypto.generators.DHKeyGeneratorHelper,
  174. // which seems a little stronger (?) than OpenSSL's (OSSL just generates a random,
  175. // while BC generates a random potential prime [for limit > 0], though it's not
  176. // subject to Miller-Rabin [certainty = 0], but is subject to other constraints)
  177. // see also [ossl]/crypto/dh/dh_key.c #generate_key
  178. if (limit == 0) {
  179. BigInteger pSub2 = p.subtract(TWO);
  180. do {
  181. x = BN.getRandomBIInRange(pSub2, secureRandom);
  182. } while (x.equals(BigInteger.ZERO));
  183. } else {
  184. do {
  185. // generate potential prime, though with 0 certainty (no Miller-Rabin tests)
  186. x = new BigInteger(limit, 0, secureRandom);
  187. } while (x.equals(BigInteger.ZERO));
  188. }
  189. return x;
  190. }
  191. public static BigInteger generateX(BigInteger p) {
  192. // OpenSSL default l(imit) is p bits - 1 -- see [ossl]/crypto/dh/dh_key.c #generate_key
  193. return generateX(p, p.bitLength() - 1);
  194. }
  195. public static BigInteger generateY(BigInteger p, BigInteger g, BigInteger x) {
  196. return g.modPow(x, p);
  197. }
  198. public static BigInteger generateY(BigInteger p, int g, BigInteger x) {
  199. return generateY(p, BigInteger.valueOf(g), x);
  200. }
  201. @JRubyMethod(name="generate_key!")
  202. public synchronized IRubyObject dh_generate_key() {
  203. BigInteger p, g, x, y;
  204. if ((p = this.dh_p) == null || (g = this.dh_g) == null) {
  205. throw newDHError(getRuntime(), "can't generate key");
  206. }
  207. if ((x = this.dh_priv_key) == null) {
  208. x = generateX(p);
  209. }
  210. y = generateY(p, g, x);
  211. this.dh_priv_key = x;
  212. this.dh_pub_key = y;
  213. return this;
  214. }
  215. @JRubyMethod(name="compute_key")
  216. public synchronized IRubyObject dh_compute_key(IRubyObject other_pub_key) {
  217. BigInteger x, y, p;
  218. if ((y = BN.getBigInteger(other_pub_key)) == null) {
  219. throw getRuntime().newArgumentError("invalid public key");
  220. }
  221. if ((x = this.dh_priv_key) == null || (p = this.dh_p) == null) {
  222. throw newDHError(getRuntime(), "can't compute key");
  223. }
  224. int plen;
  225. if ((plen = p.bitLength()) == 0 || plen > OPENSSL_DH_MAX_MODULUS_BITS) {
  226. throw newDHError(getRuntime(), "can't compute key");
  227. }
  228. return getRuntime().newString(new ByteList(computeKey(y, x, p), false));
  229. }
  230. public static byte[] computeKey(BigInteger y, BigInteger x, BigInteger p) {
  231. return y.modPow(x, p).toByteArray();
  232. }
  233. @JRubyMethod(name="public?")
  234. public IRubyObject dh_is_public() {
  235. return getRuntime().newBoolean(dh_pub_key != null);
  236. }
  237. @JRubyMethod(name="private?")
  238. public IRubyObject dh_is_private() {
  239. // FIXME! need to figure out what it means in MRI/OSSL code to
  240. // claim a DH is private if an engine is present -- doesn't really
  241. // map to Java implementation.
  242. return getRuntime().newBoolean(dh_priv_key != null /* || haveEngine */);
  243. }
  244. @JRubyMethod(name={"export", "to_pem", "to_s"})
  245. public IRubyObject dh_export() {
  246. BigInteger p, g;
  247. synchronized(this) {
  248. p = this.dh_p;
  249. g = this.dh_g;
  250. }
  251. StringWriter w = new StringWriter();
  252. try {
  253. PEMInputOutput.writeDHParameters(w, new DHParameterSpec(p, g));
  254. w.flush();
  255. w.close();
  256. } catch (NoClassDefFoundError ncdfe) {
  257. throw newDHError(getRuntime(), OpenSSLReal.bcExceptionMessage(ncdfe));
  258. } catch (IOException e) {
  259. // shouldn't happen (string/buffer io only)
  260. throw getRuntime().newIOErrorFromException(e);
  261. }
  262. return getRuntime().newString(w.toString());
  263. }
  264. @JRubyMethod(name = "to_der")
  265. public IRubyObject dh_to_der() {
  266. BigInteger p, g;
  267. synchronized (this) {
  268. p = this.dh_p;
  269. g = this.dh_g;
  270. }
  271. try {
  272. byte[] bytes = org.jruby.ext.openssl.impl.PKey.toDerDHKey(p, g);
  273. return RubyString.newString(getRuntime(), bytes);
  274. } catch (NoClassDefFoundError ncdfe) {
  275. throw newDHError(getRuntime(), OpenSSLReal.bcExceptionMessage(ncdfe));
  276. } catch (IOException ioe) {
  277. throw newDHError(getRuntime(), ioe.getMessage());
  278. }
  279. }
  280. @JRubyMethod(name="params")
  281. public IRubyObject dh_get_params() {
  282. BigInteger p, g, x, y;
  283. synchronized(this) {
  284. p = this.dh_p;
  285. g = this.dh_g;
  286. x = this.dh_priv_key;
  287. y = this.dh_pub_key;
  288. }
  289. Ruby runtime = getRuntime();
  290. HashMap<IRubyObject, IRubyObject> params = new HashMap<IRubyObject, IRubyObject>();
  291. params.put(runtime.newString("p"), BN.newBN(runtime, p));
  292. params.put(runtime.newString("g"), BN.newBN(runtime, g));
  293. params.put(runtime.newString("pub_key"), BN.newBN(runtime, x));
  294. params.put(runtime.newString("priv_key"), BN.newBN(runtime, y));
  295. return RubyHash.newHash(runtime, params, runtime.getNil());
  296. }
  297. // don't need synchronized as value is volatile
  298. @JRubyMethod(name="p")
  299. public IRubyObject dh_get_p() {
  300. return getBN(dh_p);
  301. }
  302. @JRubyMethod(name="p=")
  303. public synchronized IRubyObject dh_set_p(IRubyObject arg) {
  304. this.dh_p = BN.getBigInteger(arg);
  305. return arg;
  306. }
  307. // don't need synchronized as value is volatile
  308. @JRubyMethod(name="g")
  309. public IRubyObject dh_get_g() {
  310. return getBN(dh_g);
  311. }
  312. @JRubyMethod(name="g=")
  313. public synchronized IRubyObject dh_set_g(IRubyObject arg) {
  314. this.dh_g = BN.getBigInteger(arg);
  315. return arg;
  316. }
  317. // don't need synchronized as value is volatile
  318. @JRubyMethod(name="pub_key")
  319. public IRubyObject dh_get_pub_key() {
  320. return getBN(dh_pub_key);
  321. }
  322. @JRubyMethod(name="pub_key=")
  323. public synchronized IRubyObject dh_set_pub_key(IRubyObject arg) {
  324. this.dh_pub_key = BN.getBigInteger(arg);
  325. return arg;
  326. }
  327. // don't need synchronized as value is volatile
  328. @JRubyMethod(name="priv_key")
  329. public IRubyObject dh_get_priv_key() {
  330. return getBN(dh_priv_key);
  331. }
  332. @JRubyMethod(name="priv_key=")
  333. public synchronized IRubyObject dh_set_priv_key(IRubyObject arg) {
  334. this.dh_priv_key = BN.getBigInteger(arg);
  335. return arg;
  336. }
  337. private IRubyObject getBN(BigInteger value) {
  338. if (value != null) {
  339. return BN.newBN(getRuntime(), value);
  340. }
  341. return getRuntime().getNil();
  342. }
  343. // override differently-named abstract method from PKey
  344. public IRubyObject to_der() {
  345. return dh_to_der();
  346. }
  347. }