PageRenderTime 4080ms CodeModel.GetById 15ms RepoModel.GetById 4ms app.codeStats 0ms

/projects/jruby-1.7.3/src/org/jruby/ext/digest/RubyDigest.java

https://gitlab.com/essere.lab.public/qualitas.class-corpus
Java | 472 lines | 358 code | 65 blank | 49 comment | 28 complexity | 3519ea1b3778b4f196a2e72940666bbf MD5 | raw file
  1. /*
  2. ***** BEGIN LICENSE BLOCK *****
  3. * Version: EPL 1.0/GPL 2.0/LGPL 2.1
  4. *
  5. * The contents of this file are subject to the Common Public
  6. * License Version 1.0 (the "License"); you may not use this file
  7. * except in compliance with the License. You may obtain a copy of
  8. * the License at http://www.eclipse.org/legal/cpl-v10.html
  9. *
  10. * Software distributed under the License is distributed on an "AS
  11. * IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
  12. * implied. See the License for the specific language governing
  13. * rights and limitations under the License.
  14. *
  15. * Copyright (C) 2006, 2007 Ola Bini <ola@ologix.com>
  16. * Copyright (C) 2007 Nick Sieger <nicksieger@gmail.com>
  17. * Copyright (C) 2008 Vladimir Sizikov <vsizikov@gmail.com>
  18. * Copyright (C) 2009 Joseph LaFata <joe@quibb.org>
  19. *
  20. * Alternatively, the contents of this file may be used under the terms of
  21. * either of the GNU General Public License Version 2 or later (the "GPL"),
  22. * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
  23. * in which case the provisions of the GPL or the LGPL are applicable instead
  24. * of those above. If you wish to allow use of your version of this file only
  25. * under the terms of either the GPL or the LGPL, and not to allow others to
  26. * use your version of this file under the terms of the EPL, indicate your
  27. * decision by deleting the provisions above and replace them with the notice
  28. * and other provisions required by the GPL or the LGPL. If you do not delete
  29. * the provisions above, a recipient may use your version of this file under
  30. * the terms of any one of the EPL, the GPL or the LGPL.
  31. ***** END LICENSE BLOCK *****/
  32. package org.jruby.ext.digest;
  33. import java.security.AccessController;
  34. import java.security.MessageDigest;
  35. import java.security.NoSuchAlgorithmException;
  36. import java.security.PrivilegedAction;
  37. import java.security.Provider;
  38. import java.util.HashMap;
  39. import java.util.Map;
  40. import org.jruby.Ruby;
  41. import org.jruby.RubyClass;
  42. import org.jruby.RubyFixnum;
  43. import org.jruby.RubyModule;
  44. import org.jruby.RubyObject;
  45. import org.jruby.RubyString;
  46. import org.jruby.anno.JRubyClass;
  47. import org.jruby.anno.JRubyMethod;
  48. import org.jruby.anno.JRubyModule;
  49. import org.jruby.runtime.Block;
  50. import org.jruby.runtime.ObjectAllocator;
  51. import org.jruby.runtime.ThreadContext;
  52. import org.jruby.runtime.builtin.IRubyObject;
  53. import org.jruby.util.ByteList;
  54. /**
  55. * @author <a href="mailto:ola.bini@ki.se">Ola Bini</a>
  56. */
  57. @JRubyModule(name="Digest")
  58. public class RubyDigest {
  59. private static Provider provider = null;
  60. private static final Map<String, MessageDigest> CLONEABLE_DIGESTS = new HashMap<String, MessageDigest>();
  61. static {
  62. // standard digests from JCA specification; if we can retrieve and clone, save them
  63. for (String name : new String[] {"MD2", "MD5", "SHA-1", "SHA-256", "SHA-384", "SHA-512"})
  64. try {
  65. MessageDigest digest = MessageDigest.getInstance(name);
  66. digest.clone();
  67. CLONEABLE_DIGESTS.put(name, digest);
  68. } catch (Exception e) {
  69. e.printStackTrace();
  70. // ignore; go to next iteration
  71. }
  72. }
  73. public static void createDigest(Ruby runtime) {
  74. // We're not setting the provider or anything, but it seems that BouncyCastle does some internal things in its
  75. // provider's constructor which require it to be executed in a secure context.
  76. // Ideally this hack should be removed. See JRUBY-3919 and this BC bug:
  77. // http://www.bouncycastle.org/jira/browse/BJA-227
  78. provider = (Provider) AccessController.doPrivileged(new PrivilegedAction() {
  79. public Object run() {
  80. try {
  81. return Class.forName("org.bouncycastle.jce.provider.BouncyCastleProvider").newInstance();
  82. } catch(Throwable t) {
  83. // provider is not available
  84. return null;
  85. }
  86. }
  87. });
  88. RubyModule mDigest = runtime.defineModule("Digest");
  89. mDigest.defineAnnotatedMethods(RubyDigest.class);
  90. RubyModule mDigestInstance = mDigest.defineModuleUnder("Instance");
  91. mDigestInstance.defineAnnotatedMethods(DigestInstance.class);
  92. RubyClass cDigestClass = mDigest.defineClassUnder("Class", runtime.getObject(), DigestClass.DIGEST_CLASS_ALLOCATOR);
  93. cDigestClass.defineAnnotatedMethods(DigestClass.class);
  94. cDigestClass.includeModule(mDigestInstance);
  95. RubyClass cDigestBase = mDigest.defineClassUnder("Base", cDigestClass, DigestBase.DIGEST_BASE_ALLOCATOR);
  96. cDigestBase.defineAnnotatedMethods(DigestBase.class);
  97. }
  98. private static MessageDigest createMessageDigest(Ruby runtime, String providerName) throws NoSuchAlgorithmException {
  99. MessageDigest cloneable = CLONEABLE_DIGESTS.get(providerName);
  100. if (cloneable != null) {
  101. try {
  102. return (MessageDigest)cloneable.clone();
  103. } catch (CloneNotSupportedException cnse) {
  104. // should never happen, since we tested it in static init
  105. }
  106. }
  107. // fall back on JCA mechanisms for getting a digest
  108. if(provider != null) {
  109. try {
  110. return MessageDigest.getInstance(providerName, provider);
  111. } catch(NoSuchAlgorithmException e) {
  112. // bouncy castle doesn't support algorithm
  113. }
  114. }
  115. // fall back to default JCA providers
  116. return MessageDigest.getInstance(providerName);
  117. }
  118. private final static byte[] digits = {
  119. '0', '1', '2', '3', '4', '5',
  120. '6', '7', '8', '9', 'a', 'b',
  121. 'c', 'd', 'e', 'f', 'g', 'h',
  122. 'i', 'j', 'k', 'l', 'm', 'n',
  123. 'o', 'p', 'q', 'r', 's', 't',
  124. 'u', 'v', 'w', 'x', 'y', 'z'
  125. };
  126. private static ByteList toHex(byte[] val) {
  127. ByteList byteList = new ByteList(val.length * 2);
  128. for (int i = 0, j = val.length; i < j; i++) {
  129. int b = val[i] & 0xFF;
  130. byteList.append(digits[b >> 4]);
  131. byteList.append(digits[b & 0xF]);
  132. }
  133. return byteList;
  134. }
  135. private static IRubyObject toHexString(Ruby runtime, byte[] val) {
  136. return RubyString.newStringNoCopy(runtime, ByteList.plain(toHex(val)));
  137. }
  138. @JRubyMethod(name = "hexencode", required = 1, meta = true)
  139. public static IRubyObject s_hexencode(IRubyObject recv, IRubyObject arg) {
  140. return toHexString(recv.getRuntime(), arg.convertToString().getBytes());
  141. }
  142. private static class Metadata {
  143. private final String name;
  144. private final int blockLength;
  145. Metadata(String name, int blockLength) {
  146. this.name = name;
  147. this.blockLength = blockLength;
  148. }
  149. String getName() {
  150. return name;
  151. }
  152. int getBlockLength() {
  153. return blockLength;
  154. }
  155. }
  156. @JRubyClass(name="Digest::MD5", parent="Digest::Base")
  157. public static class MD5 {}
  158. @JRubyClass(name="Digest::RMD160", parent="Digest::Base")
  159. public static class RMD160 {}
  160. @JRubyClass(name="Digest::SHA1", parent="Digest::Base")
  161. public static class SHA1 {}
  162. @JRubyClass(name="Digest::SHA256", parent="Digest::Base")
  163. public static class SHA256 {}
  164. @JRubyClass(name="Digest::SHA384", parent="Digest::Base")
  165. public static class SHA384 {}
  166. @JRubyClass(name="Digest::SHA512", parent="Digest::Base")
  167. public static class SHA512 {}
  168. public static void createDigestMD5(Ruby runtime) {
  169. runtime.getLoadService().require("digest");
  170. RubyModule mDigest = runtime.getModule("Digest");
  171. RubyClass cDigestBase = mDigest.getClass("Base");
  172. RubyClass cDigest_MD5 = mDigest.defineClassUnder("MD5",cDigestBase,cDigestBase.getAllocator());
  173. cDigest_MD5.setInternalVariable("metadata", new Metadata("MD5", 64));
  174. }
  175. public static void createDigestRMD160(Ruby runtime) {
  176. runtime.getLoadService().require("digest");
  177. if(provider == null) {
  178. throw runtime.newLoadError("RMD160 not supported without BouncyCastle");
  179. }
  180. RubyModule mDigest = runtime.getModule("Digest");
  181. RubyClass cDigestBase = mDigest.getClass("Base");
  182. RubyClass cDigest_RMD160 = mDigest.defineClassUnder("RMD160",cDigestBase,cDigestBase.getAllocator());
  183. cDigest_RMD160.setInternalVariable("metadata", new Metadata("RIPEMD160", 64));
  184. }
  185. public static void createDigestSHA1(Ruby runtime) {
  186. runtime.getLoadService().require("digest");
  187. RubyModule mDigest = runtime.getModule("Digest");
  188. RubyClass cDigestBase = mDigest.getClass("Base");
  189. RubyClass cDigest_SHA1 = mDigest.defineClassUnder("SHA1",cDigestBase,cDigestBase.getAllocator());
  190. cDigest_SHA1.setInternalVariable("metadata", new Metadata("SHA1", 64));
  191. }
  192. public static void createDigestSHA2(Ruby runtime) {
  193. runtime.getLoadService().require("digest");
  194. try {
  195. createMessageDigest(runtime, "SHA-256");
  196. } catch(NoSuchAlgorithmException e) {
  197. throw runtime.newLoadError("SHA2 not supported");
  198. }
  199. RubyModule mDigest = runtime.getModule("Digest");
  200. RubyClass cDigestBase = mDigest.getClass("Base");
  201. RubyClass cDigest_SHA2_256 = mDigest.defineClassUnder("SHA256",cDigestBase,cDigestBase.getAllocator());
  202. Metadata sha256Metadata = new Metadata("SHA-256", 64);
  203. cDigest_SHA2_256.setInternalVariable("metadata", sha256Metadata);
  204. RubyClass cDigest_SHA2_384 = mDigest.defineClassUnder("SHA384",cDigestBase,cDigestBase.getAllocator());
  205. cDigest_SHA2_384.setInternalVariable("metadata", new Metadata("SHA-384", 128));
  206. RubyClass cDigest_SHA2_512 = mDigest.defineClassUnder("SHA512",cDigestBase,cDigestBase.getAllocator());
  207. cDigest_SHA2_512.setInternalVariable("metadata", new Metadata("SHA-512", 128));
  208. }
  209. @JRubyModule(name = "Digest::Instance")
  210. public static class DigestInstance {
  211. private static IRubyObject throwUnimplError(IRubyObject self, String name) {
  212. throw self.getRuntime().newRuntimeError(String.format("%s does not implement %s()", self.getMetaClass().getRealClass().getName(), name));
  213. }
  214. /* instance methods that should be overridden */
  215. @JRubyMethod(name = {"update", "<<"}, required = 1)
  216. public static IRubyObject update(ThreadContext ctx, IRubyObject self, IRubyObject arg) {
  217. return throwUnimplError(self, "update");
  218. }
  219. @JRubyMethod()
  220. public static IRubyObject finish(ThreadContext ctx, IRubyObject self) {
  221. return throwUnimplError(self, "finish");
  222. }
  223. @JRubyMethod()
  224. public static IRubyObject reset(ThreadContext ctx, IRubyObject self) {
  225. return throwUnimplError(self, "reset");
  226. }
  227. @JRubyMethod()
  228. public static IRubyObject digest_length(ThreadContext ctx, IRubyObject self) {
  229. return digest(ctx, self, null).convertToString().length();
  230. }
  231. @JRubyMethod()
  232. public static IRubyObject block_length(ThreadContext ctx, IRubyObject self) {
  233. return throwUnimplError(self, "block_length");
  234. }
  235. /* instance methods that may be overridden */
  236. @JRubyMethod(name = "==", required = 1)
  237. public static IRubyObject op_equal(ThreadContext ctx, IRubyObject self, IRubyObject oth) {
  238. RubyString str1, str2;
  239. RubyModule instance = (RubyModule)self.getRuntime().getModule("Digest").getConstantAt("Instance");
  240. if (oth.getMetaClass().getRealClass().hasModuleInHierarchy(instance)) {
  241. str1 = digest(ctx, self, null).convertToString();
  242. str2 = digest(ctx, oth, null).convertToString();
  243. } else {
  244. str1 = to_s(ctx, self).convertToString();
  245. str2 = oth.convertToString();
  246. }
  247. boolean ret = str1.length().eql(str2.length()) && (str1.eql(str2));
  248. return ret ? self.getRuntime().getTrue() : self.getRuntime().getFalse();
  249. }
  250. @JRubyMethod()
  251. public static IRubyObject inspect(ThreadContext ctx, IRubyObject self) {
  252. return RubyString.newStringNoCopy(self.getRuntime(), ByteList.plain("#<" + self.getMetaClass().getRealClass().getName() + ": " + hexdigest(ctx, self, null) + ">"));
  253. }
  254. /* instance methods that need not usually be overridden */
  255. @JRubyMethod(name = "new")
  256. public static IRubyObject newObject(ThreadContext ctx, IRubyObject self) {
  257. return self.rbClone().callMethod(ctx, "reset");
  258. }
  259. @JRubyMethod(optional = 1)
  260. public static IRubyObject digest(ThreadContext ctx, IRubyObject self, IRubyObject[] args) {
  261. IRubyObject value = null;
  262. if (args != null && args.length > 0) {
  263. self.callMethod(ctx, "reset");
  264. self.callMethod(ctx, "update", args[0]);
  265. value = self.callMethod(ctx, "finish");
  266. self.callMethod(ctx, "reset");
  267. } else {
  268. IRubyObject clone = self.rbClone();
  269. value = clone.callMethod(ctx, "finish");
  270. clone.callMethod(ctx, "reset");
  271. }
  272. return value;
  273. }
  274. @JRubyMethod(name = "digest!")
  275. public static IRubyObject digest_bang(ThreadContext ctx, IRubyObject self) {
  276. IRubyObject value = self.callMethod(ctx, "finish");
  277. self.callMethod(ctx, "reset");
  278. return value;
  279. }
  280. @JRubyMethod(optional = 1)
  281. public static IRubyObject hexdigest(ThreadContext ctx, IRubyObject self, IRubyObject[] args) {
  282. return toHexString(ctx.runtime, digest(ctx, self, args).convertToString().getBytes());
  283. }
  284. @JRubyMethod(name = "hexdigest!")
  285. public static IRubyObject hexdigest_bang(ThreadContext ctx, IRubyObject self) {
  286. return toHexString(ctx.runtime, digest_bang(ctx, self).convertToString().getBytes());
  287. }
  288. @JRubyMethod()
  289. public static IRubyObject to_s(ThreadContext ctx, IRubyObject self) {
  290. return self.callMethod(ctx, "hexdigest");
  291. }
  292. @JRubyMethod(name = {"length", "size"})
  293. public static IRubyObject length(ThreadContext ctx, IRubyObject self) {
  294. return self.callMethod(ctx, "digest_length");
  295. }
  296. }
  297. @JRubyClass(name="Digest::Class")
  298. public static class DigestClass extends RubyObject {
  299. protected static final ObjectAllocator DIGEST_CLASS_ALLOCATOR = new ObjectAllocator() {
  300. public IRubyObject allocate(Ruby runtime, RubyClass klass) {
  301. return new DigestClass(runtime, klass);
  302. }
  303. };
  304. public DigestClass(Ruby runtime, RubyClass type) {
  305. super(runtime, type);
  306. }
  307. @JRubyMethod(name = "digest", required = 1, rest = true, meta = true)
  308. public static IRubyObject s_digest(ThreadContext ctx, IRubyObject recv, IRubyObject[] args, Block unusedBlock) {
  309. Ruby runtime = recv.getRuntime();
  310. if (args.length < 1) {
  311. throw runtime.newArgumentError("no data given");
  312. }
  313. RubyString str = args[0].convertToString();
  314. IRubyObject[] newArgs = new IRubyObject[args.length - 1];
  315. System.arraycopy(args, 1, newArgs, 0, args.length - 1);
  316. IRubyObject obj = ((RubyClass)recv).newInstance(ctx, newArgs, Block.NULL_BLOCK);
  317. return obj.callMethod(ctx, "digest", str);
  318. }
  319. @JRubyMethod(name = "hexdigest", required = 1, optional = 1, meta = true)
  320. public static IRubyObject s_hexdigest(ThreadContext ctx, IRubyObject recv, IRubyObject[] args, Block unusedBlock) {
  321. Ruby runtime = recv.getRuntime();
  322. byte[] digest = recv.callMethod(ctx, "digest", args, Block.NULL_BLOCK).convertToString().getBytes();
  323. return RubyDigest.toHexString(runtime, digest);
  324. }
  325. }
  326. @JRubyClass(name="Digest::Base")
  327. public static class DigestBase extends RubyObject {
  328. protected static final ObjectAllocator DIGEST_BASE_ALLOCATOR = new ObjectAllocator() {
  329. public IRubyObject allocate(Ruby runtime, RubyClass klass) {
  330. return new DigestBase(runtime, klass);
  331. }
  332. };
  333. private MessageDigest algo;
  334. private int blockLength = 0;
  335. public DigestBase(Ruby runtime, RubyClass type) {
  336. super(runtime,type);
  337. if(type == runtime.getModule("Digest").getClass("Base")) {
  338. throw runtime.newNotImplementedError("Digest::Base is an abstract class");
  339. }
  340. Metadata metadata = getMetadata(type);
  341. if(metadata == null) {
  342. throw runtime.newNotImplementedError("the " + type + "() function is unimplemented on this machine");
  343. }
  344. try {
  345. setAlgorithm(metadata);
  346. } catch(NoSuchAlgorithmException e) {
  347. throw runtime.newNotImplementedError("the " + type + "() function is unimplemented on this machine");
  348. }
  349. }
  350. // if subclass extends particular digest we need to walk to find it...we should rearchitect digest to avoid walking type systems
  351. private Metadata getMetadata(RubyModule type) {
  352. for (RubyModule current = type; current != null; current = current.getSuperClass()) {
  353. Metadata metadata = (Metadata) current.getInternalVariable("metadata");
  354. if (metadata != null) return metadata;
  355. }
  356. return null;
  357. }
  358. @JRubyMethod(required = 1)
  359. @Override
  360. public IRubyObject initialize_copy(IRubyObject obj) {
  361. if(this == obj) {
  362. return this;
  363. }
  364. ((RubyObject)obj).checkFrozen();
  365. String name = ((DigestBase)obj).algo.getAlgorithm();
  366. try {
  367. algo = (MessageDigest)((DigestBase)obj).algo.clone();
  368. } catch(CloneNotSupportedException e) {
  369. throw getRuntime().newTypeError("Could not initialize copy of digest (" + name + ")");
  370. }
  371. return this;
  372. }
  373. @JRubyMethod(name = {"update", "<<"}, required = 1)
  374. public IRubyObject update(IRubyObject obj) {
  375. ByteList bytes = obj.convertToString().getByteList();
  376. algo.update(bytes.getUnsafeBytes(), bytes.getBegin(), bytes.getRealSize());
  377. return this;
  378. }
  379. @JRubyMethod()
  380. public IRubyObject finish() {
  381. IRubyObject digest = RubyString.newStringNoCopy(getRuntime(), algo.digest());
  382. algo.reset();
  383. return digest;
  384. }
  385. @JRubyMethod()
  386. public IRubyObject digest_length() {
  387. return RubyFixnum.newFixnum(getRuntime(), algo.getDigestLength());
  388. }
  389. @JRubyMethod()
  390. public IRubyObject block_length() {
  391. if (blockLength == 0) {
  392. throw getRuntime().newRuntimeError(
  393. this.getMetaClass() + " doesn't implement block_length()");
  394. }
  395. return RubyFixnum.newFixnum(getRuntime(), blockLength);
  396. }
  397. @JRubyMethod()
  398. public IRubyObject reset() {
  399. algo.reset();
  400. return this;
  401. }
  402. private void setAlgorithm(Metadata metadata) throws NoSuchAlgorithmException {
  403. this.algo = createMessageDigest(getRuntime(), metadata.getName());
  404. this.blockLength = metadata.getBlockLength();
  405. }
  406. }
  407. }// RubyDigest