package org.red5.server.crypto;
/*
* RED5 Open Source Flash Server - http://www.osflash.org/red5
*
* Copyright (c) 2006-2009 by respective authors (see below). All rights reserved.
*
* This library is free software; you can redistribute it and/or modify it under the
* terms of the GNU Lesser General Public License as published by the Free Software
* Foundation; either version 2.1 of the License, or (at your option) any later
* version.
*
* This library 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. See the GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License along
* with this library; if not, write to the Free Software Foundation, Inc.,
* 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
import java.io.BufferedInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.security.Key;
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import org.red5.logging.Red5LoggerFactory;
import org.slf4j.Logger;
/**
* HMAC - a little utility to compute HMACs on data; the data and key may be
* obtained from files, from the command line, or from the clip board. Note that
* this little program is really only suitable for files of very modest length,
* because it attempts to load the entire file into a byte array.
* <br />
* Original code found on javafaq.nu, no author noted.
* This version modified specifically for Red5.
*
* @author Paul Gregoire (mondain@gmail.com)
*/
public class HMAC {
protected static Logger log = Red5LoggerFactory.getLogger(HMAC.class);
public static final String ALGORITHM_ID = "HMacMD5";
public static final int MIN_LENGTH = 8;
public static final int BUF_LENGTH = 256;
private byte[] keyBytes;
private byte[] dataBytes;
protected boolean noHex = false;
protected boolean reverse = false;
/**
* Return true if the input argument character is a digit, a space, or A-F.
* @param c input
* @return true if the input argument character is a digit, a space, or A-F.
*/
public static final boolean isHexStringChar(char c) {
return (Character.isDigit(c) || Character.isWhitespace(c) || (("0123456789abcdefABCDEF"
.indexOf(c)) >= 0));
}
/**
* Return true if the argument string seems to be a Hex data string, like
* "a0 13 2f ". Whitespace is ignored.
* @param sampleData sample data
* @return true if the argument string seems to be a Hex data string, like
* "a0 13 2f ". Whitespace is ignored.
*/
public static final boolean isHex(String sampleData) {
for (int i = 0; i < sampleData.length(); i++) {
if (!isHexStringChar(sampleData.charAt(i)))
return false;
}
return true;
}
/**
* Return true if the argument byte array seems to be a Hex data string,
* like "a0 13 2f ". Only check as far as a supplied length; if it seems to
* be hex for that many (ascii) bytes, then return true.
* @param sampleData sample data
* @param len length
*
* @return true if the argument byte array seems to be a Hex data string,
* like "a0 13 2f ". Only check as far as a supplied length; if it seems to
* be hex for that many (ascii) bytes, then return true.
*/
public static final boolean isHex(byte[] sampleData, int len) {
for (int i = 0; i < len; i++) {
if (!isHexStringChar((char) (sampleData[i]))) {
return false;
}
}
return true;
}
static final String hexDigitChars = "0123456789abcdef";
/**
* Convert a hex string into an array of bytes. The hex string can be all
* digits, or 1-octet groups separated by blanks, or any mix thereof.
*
* @param str String to be converted
* @param rev reverse
* @return bytes
*/
public static final byte[] hexToByteArray(String str, boolean rev) {
StringBuffer acc = new StringBuffer(str.length() + 1);
int cx, rp, ff, val;
char[] s = new char[str.length()];
str.toLowerCase().getChars(0, str.length(), s, 0);
for (cx = str.length() - 1, ff = 0; cx >= 0; cx--) {
if (hexDigitChars.indexOf(s[cx]) >= 0) {
acc.append(s[cx]);
ff++;
} else {
if ((ff % 2) > 0) {
acc.append('0');
}
ff = 0;
}
}
if ((ff % 2) > 0) {
acc.append('0');
}
// System.out.println("Intermediate SB value is '" + acc.toString() +
// "'");
byte[] ret = new byte[acc.length() / 2];
for (cx = 0, rp = ret.length - 1; cx < acc.length(); cx++, rp--) {
val = hexDigitChars.indexOf(acc.charAt(cx));
cx++;
val += 16 * hexDigitChars.indexOf(acc.charAt(cx));
ret[rp] = (byte) val;
}
if (rev) {
byte tmp;
int fx, bx;
for (fx = 0, bx = ret.length - 1; fx < (ret.length / 2); fx++, bx--) {
tmp = ret[bx];
ret[bx] = ret[fx];
ret[fx] = tmp;
}
}
return ret;
}
/**
* Convert a byte array to a hex string of the format "1f 30 b7".
* @param a array to convert
* @return hex string
*/
public static final String byteArrayToHex(byte[] a) {
int hn, ln, cx;
StringBuffer buf = new StringBuffer(a.length * 2);
for (cx = 0; cx < a.length; cx++) {
hn = ((int) (a[cx]) & 0x00ff) / 16;
ln = ((int) (a[cx]) & 0x000f);
buf.append(hexDigitChars.charAt(hn));
buf.append(hexDigitChars.charAt(ln));
buf.append(' ');
}
return buf.toString();
}
/**
* Accept a file name, and read that file as a string or as raw data (read
* first N bytes to check it out). If the file can't be read, then return
* null. If we can't read at least minValidLength bytes, then we declare
* that the file is invalid and return null. All the real work is done in
* readDataStream.
* @param f file
* @param minValidLength minimum valid length
* @return bytes
*
* @see #readDataStream
*/
public byte[] readDataFile(File f, int minValidLength) {
InputStream fr = null;
try {
if (f.getName().equals("-")) {
fr = System.in;
} else {
fr = new FileInputStream(f);
}
} catch (IOException ie) {
log.warn("Could not read file {}", f, ie);
return null;
}
log.debug("Reading file data from {}", f);
return readDataStream(fr, minValidLength);
}
public byte[] readDataStream(InputStream is, int minValidLength) {
int cc;
byte[] buf = new byte[BUF_LENGTH];
is = new BufferedInputStream(is);
try {
cc = is.read(buf, 0, minValidLength);
if (cc < minValidLength)
return null;
} catch (IOException ie) {
log.warn("Could not read initial data", ie);
try {
is.close();
} catch (Exception e) {
}
return null;
}
boolean ishex = (noHex) ? (false) : (isHex(buf, cc));
ByteArrayOutputStream baos = new ByteArrayOutputStream();
baos.write(buf, 0, cc);
while (cc > 0) {
try {
cc = is.read(buf);
if (cc > 0)
baos.write(buf, 0, cc);
} catch (IOException ie) {
log.warn("Error read stream data", ie);
try {
is.close();
} catch (Exception e) {
}
return null;
}
}
try {
is.close();
} catch (IOException ie2) {
}
byte[] result = baos.toByteArray();
log.debug("Read {} bytes", result.length);
if (ishex) {
result = hexToByteArray(new String(result), reverse);
}
return result;
}
/**
* compute the MAC from the member arrays keyBytes and the dataBytes. Return
* the MAC.
* @return the MAC
*/
public byte[] computeMac() {
Mac hm = null;
byte[] result = null;
if (log.isDebugEnabled()) {
log.debug("Key data: {}", byteArrayToHex(keyBytes));
log.debug("Hash data: {}", byteArrayToHex(dataBytes));
log.debug("Algorithm: {}", ALGORITHM_ID);
}
try {
hm = Mac.getInstance(ALGORITHM_ID);
Key k1 = new SecretKeySpec(keyBytes, 0, keyBytes.length, ALGORITHM_ID);
hm.init(k1);
result = hm.doFinal(dataBytes);
} catch (Exception e) {
log.warn("Bad algorithm or crypto library problem", e);
}
return result;
}
}