/* * Copyright (c) 1998-2011 Caucho Technology -- all rights reserved * * This file is part of Resin(R) Open Source * * Each copy or derived work must preserve the copyright notice and this * notice unmodified. * * Resin Open Source is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * Resin Open Source is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE, or any warranty * of NON-INFRINGEMENT. See the GNU General Public License for more * details. * * You should have received a copy of the GNU General Public License * along with Resin Open Source; if not, write to the * * Free Software Foundation, Inc. * 59 Temple Place, Suite 330 * Boston, MA 02111-1307 USA * * @author Scott Ferguson */ package com.caucho.quercus.lib; import com.caucho.config.ConfigException; import com.caucho.quercus.annotation.Optional; import com.caucho.quercus.env.*; import com.caucho.quercus.module.*; import com.caucho.util.*; import com.caucho.vfs.*; import java.io.*; import java.security.*; import java.util.*; import java.util.logging.Level; import java.util.logging.Logger; import javax.crypto.*; import javax.crypto.spec.*; /** * Hash functions. * * This module uses the {@link MessageDigest} class to calculate * digests. Typical java installations support MD2, MD5, SHA1, SHA256, SHA384, * and SHA512. */ public class HashModule extends AbstractQuercusModule { private static final L10N L = new L10N(HashModule.class); private static final Logger log = Logger.getLogger(HashModule.class.getName()); public static final int HASH_HMAC = 1; private static HashMap<String,String> _algorithmMap = new HashMap<String,String>(); public HashModule() { } public String []getLoadedExtensions() { return new String[] { "hash" }; } /** * Hashes a string */ public Value hash(Env env, String algorithm, StringValue string, @Optional boolean isBinary) { try { algorithm = getAlgorithm(algorithm); MessageDigest digest = MessageDigest.getInstance(algorithm); int len = string.length(); for (int i = 0; i < len; i++) { digest.update((byte) string.charAt(i)); } byte []bytes = digest.digest(); return hashToValue(env, bytes, isBinary); } catch (NoSuchAlgorithmException e) { env.error(L.l("'{0}' is an unknown algorithm", algorithm), e); return BooleanValue.FALSE; } } /** * Returns the list of known algorithms */ public static Value hash_algos(Env env) { ArrayValue array = new ArrayValueImpl(); for (String name : _algorithmMap.keySet()) { array.put(env.createString(name)); } Collection<String> values = _algorithmMap.values(); for (String name : Security.getAlgorithms("MessageDigest")) { if (! values.contains(name)) array.put(env.createString(name)); } return array; } /** * Copies a hash instance */ public HashContext hash_copy(HashContext context) { if (context != null) return context.copy(); else return null; } /** * Hashes a file */ public Value hash_file(Env env, String algorithm, Path path, @Optional boolean isBinary) { try { algorithm = getAlgorithm(algorithm); MessageDigest digest = MessageDigest.getInstance(algorithm); TempBuffer tempBuffer = TempBuffer.allocate(); byte []buffer = tempBuffer.getBuffer(); ReadStream is = path.openRead(); try { int len; while ((len = is.read(buffer, 0, buffer.length)) > 0) { digest.update(buffer, 0, len); } byte []bytes = digest.digest(); return hashToValue(env, bytes, isBinary); } finally { TempBuffer.free(tempBuffer); is.close(); } } catch (NoSuchAlgorithmException e) { env.error(L.l("'{0}' is an unknown algorithm", algorithm), e); return BooleanValue.FALSE; } catch (IOException e) { env.error(L.l("'{0}' is an unknown file", path), e); return BooleanValue.FALSE; } } /** * Returns the final hash value */ public Value hash_final(Env env, HashContext context, @Optional boolean isBinary) { if (context == null) return BooleanValue.FALSE; return hashToValue(env, context.digest(), isBinary); } /** * Hashes a string with the algorithm. */ public Value hash_hmac(Env env, String algorithm, StringValue data, StringValue key, @Optional boolean isBinary) { algorithm = getAlgorithm(algorithm); HashContext context = hash_init(env, algorithm, HASH_HMAC, key); hash_update(env, context, data); return hash_final(env, context, isBinary); } /** * Hashes a file with the algorithm. */ public Value hash_hmac_file(Env env, String algorithm, Path path, StringValue key, @Optional boolean isBinary) { algorithm = getAlgorithm(algorithm); HashContext context = hash_init(env, algorithm, HASH_HMAC, key); hash_update_file(env, context, path); return hash_final(env, context, isBinary); } /** * Initialize a hash context. */ public HashContext hash_init(Env env, String algorithm, @Optional int options, @Optional StringValue keyString) { try { algorithm = getAlgorithm(algorithm); if (options == HASH_HMAC) { algorithm = "Hmac" + algorithm; Mac mac = Mac.getInstance(algorithm); int keySize = 64; // php/530c if (keyString != null) keySize = keyString.length(); byte []keyBytes = new byte[keySize]; for (int i = 0; i < keyString.length(); i++) { keyBytes[i] = (byte) keyString.charAt(i); } Key key = new SecretKeySpec(keyBytes, "dsa"); mac.init(key); return new HashMacContext(mac); } else { MessageDigest md = MessageDigest.getInstance(algorithm); return new HashDigestContext(md); } } catch (Exception e) { env.error(L.l("hash_init: '{0}' is an unknown algorithm", algorithm)); return null; } } /** * Updates the hash with more data */ public Value hash_update(Env env, HashContext context, StringValue value) { if (context == null) return BooleanValue.FALSE; context.update(value); return BooleanValue.TRUE; } /** * Updates the hash with more data */ public Value hash_update_file(Env env, HashContext context, Path path) { if (context == null) return BooleanValue.FALSE; TempBuffer tempBuffer = TempBuffer.allocate(); byte []buffer = tempBuffer.getBuffer(); ReadStream is = null; try { is = path.openRead(); int len; while ((len = is.read(buffer, 0, buffer.length)) > 0) { context.update(buffer, 0, len); } } catch (IOException e) { log.log(Level.WARNING, e.toString(), e); } finally { TempBuffer.free(tempBuffer); if (is != null) is.close(); } return BooleanValue.TRUE; } /** * Updates the hash with more data */ public int hash_update_stream(Env env, HashContext context, InputStream is, @Optional("-1") int length) { if (context == null) return -1; if (length < 0) length = Integer.MAX_VALUE - 1; TempBuffer tempBuffer = TempBuffer.allocate(); byte []buffer = tempBuffer.getBuffer(); int readLength = 0; try { while (length > 0) { int sublen = buffer.length; if (length < sublen) sublen = length; int len = is.read(buffer, 0, sublen); if (len < 0) return readLength; context.update(buffer, 0, len); readLength += len; length -= len; } } catch (IOException e) { log.log(Level.WARNING, e.toString(), e); } finally { TempBuffer.free(tempBuffer); } return readLength; } // XXX: hash_update_file // XXX: hash_update_stream // XXX: hash_update // XXX: hash private static Value hashToValue(Env env, byte []bytes, boolean isBinary) { if (isBinary) { StringValue v = env.createBinaryBuilder(); v.append(bytes, 0, bytes.length); return v; } else { StringValue v = env.createUnicodeBuilder(); for (int i = 0; i < bytes.length; i++) { int ch = bytes[i]; int d1 = (ch >> 4) & 0xf; int d2 = (ch) & 0xf; if (d1 < 10) v.append((char) ('0' + d1)); else v.append((char) ('a' + d1 - 10)); if (d2 < 10) v.append((char) ('0' + d2)); else v.append((char) ('a' + d2 - 10)); } return v; } } private static String getAlgorithm(String algorithm) { String name = _algorithmMap.get(algorithm); if (name != null) return name; else return algorithm; } public abstract static class HashContext { abstract void update(StringValue value); abstract void update(byte []buffer, int offset, int length); abstract byte []digest(); abstract HashContext copy(); } public static class HashDigestContext extends HashContext { private MessageDigest _digest; HashDigestContext(MessageDigest digest) { _digest = digest; } MessageDigest getDigest() { return _digest; } void update(byte value) { _digest.update(value); } void update(StringValue value) { int len = value.length(); MessageDigest digest = _digest; for (int i = 0; i < len; i++) { digest.update((byte) value.charAt(i)); } } void update(byte []buffer, int offset, int length) { _digest.update(buffer, offset, length); } byte []digest() { return _digest.digest(); } HashContext copy() { try { return new HashDigestContext((MessageDigest) _digest.clone()); } catch (Exception e) { log.log(Level.FINE, e.toString(), e); return null; } } public String toString() { return (getClass().getSimpleName() + "[" + _digest + "]"); } } public static class HashMacContext extends HashContext { private Mac _digest; HashMacContext(Mac digest) { _digest = digest; } void update(byte value) { _digest.update(value); } void update(StringValue value) { int len = value.length(); Mac digest = _digest; TempBuffer tBuf = TempBuffer.allocate(); byte []buffer = tBuf.getBuffer(); int offset = 0; while (offset < len) { int sublen = len - offset; if (buffer.length < sublen) sublen = buffer.length; for (int i = 0; i < sublen; i++) { buffer[i] = (byte) value.charAt(offset + i); } digest.update(buffer, 0, sublen); offset += sublen; } TempBuffer.free(tBuf); } void update(byte []buffer, int offset, int length) { _digest.update(buffer, offset, length); } byte []digest() { return _digest.doFinal(); } HashContext copy() { try { return new HashDigestContext((MessageDigest) _digest.clone()); } catch (Exception e) { log.log(Level.FINE, e.toString(), e); return null; } } public String toString() { return (getClass().getSimpleName() + "[" + _digest + "]"); } } static { _algorithmMap.put("md2", "MD2"); _algorithmMap.put("md5", "MD5"); _algorithmMap.put("sha1", "SHA"); _algorithmMap.put("sha256", "SHA-256"); _algorithmMap.put("sha384", "SHA-384"); _algorithmMap.put("sha512", "SHA-512"); } }