/*******************************************************************************
* Copyright (c) 2013 GigaSpaces Technologies Ltd. All rights reserved
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
* an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
* specific language governing permissions and limitations under the License.
*******************************************************************************/
package org.cloudifysource.dsl.internal.tools.download;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.text.MessageFormat;
import java.text.ParseException;
import java.util.ArrayList;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.apache.commons.codec.binary.Hex;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang.StringUtils;
/**
* This class is used to validate a file's checksum against a validation hash file containing the valid checksum.
* Checksum algorithms include md5, sha1, sha256, sha384 and sha512.
*
* @author adaml
* @since 2.6.0
*
*/
public class ChecksumVerifier {
/**
* checksum constant enum for maintaining the digest message algorithm id.
* see {@link java.security.MessageDigest}. the enum exposes a static getAlgorithm
* method used to determine hashing id according to it's file extension.
*
*/
public enum ChecksumAlgorithm {
/**
* md5 hashing id.
*/
MD5("MD5"),
/**
* sha-1 hashing id.
*/
SHA1("SHA-1"),
/**
* sha-256 hashing id.
*/
SHA256("SHA-256"),
/**
* sha-384 hashing id.
*/
SHA384("SHA-384"),
/**
* sha-512 hashing id.
*/
SHA512("SHA-512");
private String algorithm;
ChecksumAlgorithm(final String algorithm) {
this.algorithm = algorithm;
}
public String getValue() {
return algorithm;
}
/**
* returns the hashing algorithm's hashing id {@link java.security.MessageDigest}
* according to file extension. the file extension should indicate the message hashing,
* for example: 'md5'/'sha1'.
* @param ext
* the file extension.
* @return
* returns the MessageDigest hashing id if found else returns null.
*/
public static String toAlgorithm(final String ext) {
for (ChecksumAlgorithm cs : ChecksumAlgorithm.values()) {
if (cs.getValue().replaceAll("-", "").equalsIgnoreCase(ext)) {
return cs.getValue();
}
}
return null;
}
public static List<String> names() {
List<String> algoNames = new ArrayList<String>();
for (ChecksumAlgorithm algo : ChecksumAlgorithm.values()) {
algoNames.add(algo.name());
}
return algoNames;
}
}
private static final Logger logger = Logger
.getLogger(ChecksumVerifier.class.getName());
private File hashFile;
private File file;
private MessageFormat format = new MessageFormat("{0} *{1}");
public File getHashFile() {
return hashFile;
}
public void setHashFile(final File hashFile) {
this.hashFile = hashFile;
}
public File getFile() {
return file;
}
public void setFile(final File file) {
this.file = file;
}
public void setFormat(final MessageFormat format) {
this.format = format;
}
public MessageFormat getFormat() {
return this.format;
}
/**
* evaluates the file checksum against the given hash file.
* @return
* true if checksum matches else returns false.
* @throws ChecksumVerifierException
* in case of an exception during the evaluation process.
*/
public boolean evaluate()
throws ChecksumVerifierException {
final String resourceHash = calculateFileDigest();
String checksum;
logger.log(Level.FINE, "Checksum result for " + this.file.getPath() + " is " + resourceHash);
checksum = readChecksum(this.hashFile);
if (StringUtils.isEmpty(checksum)) {
throw new ChecksumVerifierException("hash file does not contain any data");
}
if (StringUtils.isEmpty(resourceHash)) {
throw new ChecksumVerifierException("resource hash calculation failed. "
+ " This should not happen");
}
if (!checksum.equals(resourceHash)) {
throw new ChecksumVerifierException("checksum validation failed. File hash is " + resourceHash
+ " while checksum is " + checksum);
}
logger.info("File verification completed successfully.");
return true;
}
/**
* calculates the file hash.
* @return
* the file hash.
* @throws ChecksumVerifierException
* if calculation fails.
*
*/
public String calculateFileDigest() throws ChecksumVerifierException {
final String hashFileName = this.hashFile.getName();
final String hashFileExt = getFileExtention(hashFileName);
final String checksumAlgorithm = ChecksumAlgorithm.toAlgorithm(hashFileExt);
if (checksumAlgorithm == null) {
throw new ChecksumVerifierException("Validation checksum method " + hashFileExt + " is not supported."
+ " Hash file extention should match one of the following values: "
+ ChecksumAlgorithm.names());
}
MessageDigest messageDigest;
try {
messageDigest = MessageDigest.getInstance(checksumAlgorithm);
} catch (NoSuchAlgorithmException e) {
throw new ChecksumVerifierException("Algorithm " + checksumAlgorithm
+ " does not exist or is not supported.", e);
}
if (messageDigest == null) {
throw new ChecksumVerifierException("Unable to create Message Digest for algorithm " + checksumAlgorithm);
}
final byte[] buffer = new byte[(int) this.file.length()];
FileInputStream fis = null;
try {
fis = new FileInputStream(this.file);
fis.read(buffer);
} catch (FileNotFoundException e) {
logger.warning("Could not find file to digest.");
throw new IllegalStateException("Resource was not found.", e);
} catch (IOException e) {
throw new ChecksumVerifierException("Failed calculating file hash.", e);
} finally {
IOUtils.closeQuietly(fis);
}
messageDigest.reset();
messageDigest.update(buffer);
final byte[] digest = messageDigest.digest();
return Hex.encodeHexString(digest);
}
private String getFileExtention(final String resourceName) {
String extension = "";
int i = resourceName.lastIndexOf('.');
if (i > 0) {
extension = resourceName.substring(i + 1);
}
return extension;
}
private String readChecksum(final File checksumFile)
throws ChecksumVerifierException {
BufferedReader br = null;
try {
br = new BufferedReader(new FileReader(checksumFile));
logger.fine("reading checksum from file " + checksumFile);
final Object[] result = format.parse(br.readLine());
if (result == null || result.length == 0 || result[0] == null) {
throw new ChecksumVerifierException("a checksum hash could not be found.");
}
return (String) result[0];
} catch (IOException e) {
throw new ChecksumVerifierException("Couldn't read checksum file " + checksumFile, e);
} catch (ParseException e) {
throw new ChecksumVerifierException("Couldn't parse checksum file " + checksumFile, e);
} finally {
IOUtils.closeQuietly(br);
}
}
}