/*
* 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");
}
}