package eu.tpmusielak.securephoto.container; import eu.tpmusielak.securephoto.verification.VerificationFactorData; import eu.tpmusielak.securephoto.verification.Verifier; import javax.xml.bind.DatatypeConverter; import java.io.*; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.util.*; /** * Created by IntelliJ IDEA. * User: Tomasz P. Musielak * Date: 09.02.12 * Time: 01:43 */ public final class SPImage implements Serializable, SPIFile { private static final long serialVersionUID = -8843183001340004635L; public final static String DIGEST_ALGORITHM = "SHA-1"; public final static int ID_LENGTH = 20; public final static String DEFAULT_EXTENSION = "spi"; private final byte[] imageData; private List<Class<Verifier>> verificationFactors; private Map<Class<Verifier>, VerificationFactorData> verificationFactorData; private SPImageHeader header; public SPImageHeader getHeader() { return header; } private SPImage(byte[] imageData, byte[] inputHash) { header = new SPImageHeader(); if (inputHash == null) inputHash = new byte[0]; //Generate unique frame ID Random random = new Random(); byte[] idBytes = new byte[20]; random.nextBytes(idBytes); header.uniqueFrameID = idBytes; byte[] mImageHash; this.imageData = imageData; try { MessageDigest messageDigest = MessageDigest.getInstance(DIGEST_ALGORITHM); messageDigest.update(imageData); messageDigest.update(inputHash); mImageHash = messageDigest.digest(); } catch (NoSuchAlgorithmException e) { throw new RuntimeException(String.format("Digest algorighm %s is not available", DIGEST_ALGORITHM)); } header.frameHash = mImageHash; verificationFactors = new ArrayList<Class<Verifier>>(); verificationFactorData = new HashMap<Class<Verifier>, VerificationFactorData>(); } private SPImage(byte[] imageData) { this(imageData, null); } public static SPImage getInstance(byte[] imageData) { return getInstance(imageData, null); } public static SPImage getInstance(byte[] imageData, List<Verifier> verifiers, byte[] inputHash) { SPImage image = new SPImage(imageData, inputHash); MessageDigest messageDigest = null; try { messageDigest = MessageDigest.getInstance(DIGEST_ALGORITHM); } catch (NoSuchAlgorithmException ignored) { throw new RuntimeException(String.format("Digest algorighm %s is not available", DIGEST_ALGORITHM)); } if (verifiers != null) { for (Verifier v : verifiers) { Class<Verifier> verifierClass = (Class<Verifier>) v.getClass(); VerificationFactorData data = v.onCapture(image); // Add verification factors and data to file image.verificationFactors.add(verifierClass); image.verificationFactorData.put(verifierClass, data); // Recompute frame hash including the verifier data // Update frame hash messageDigest.update(image.header.frameHash); messageDigest.update(data.getHash()); image.header.frameHash = messageDigest.digest(); } } return image; } @SuppressWarnings("unchecked") public static SPImage getInstance(byte[] imageData, List<Verifier> verifiers) { return getInstance(imageData, verifiers, null); } public static SPImage fromBytes(byte[] bytes) throws IOException, ClassNotFoundException { ByteArrayInputStream byteArrayInput = new ByteArrayInputStream(bytes); ObjectInput objectInput = new ObjectInputStream(byteArrayInput); // Read and discard the header SPImageHeader header = (SPImageHeader) objectInput.readObject(); objectInput = new ObjectInputStream(byteArrayInput); // OIS workaround SPImage image = (SPImage) objectInput.readObject(); objectInput.close(); byteArrayInput.close(); return image; } public static SPImage fromFile(File file) throws IOException, ClassNotFoundException { FileInputStream inputStream = new FileInputStream(file); long fileLength = file.length(); byte[] bytes = new byte[(int) fileLength]; int bytesRead = 0; bytesRead = inputStream.read(bytes); inputStream.close(); if (bytesRead != fileLength) throw new IOException("Could not read the entire file"); return fromBytes(bytes); } public static byte[] extractImageData(File file) throws ClassNotFoundException, IOException { SPImage image = fromFile(file); return image.getImageData(); } public byte[] getImageData() { return imageData; } public byte[] getFrameHash() { return header.frameHash; } public byte[] getUniqueFrameID() { return header.uniqueFrameID; } public List<Class<Verifier>> getVerificationFactors() { return verificationFactors; } public Map<Class<Verifier>, VerificationFactorData> getVerificationFactorData() { return verificationFactorData; } public byte[] toByteArray() { byte[] bytes = null; try { ByteArrayOutputStream byteArrayOutput = new ByteArrayOutputStream(); ObjectOutputStream objectOutput = new ObjectOutputStream(byteArrayOutput); objectOutput.writeObject(SPImage.this); byte[] frameBytes = byteArrayOutput.toByteArray(); header.size = frameBytes.length; byteArrayOutput.reset(); objectOutput = new ObjectOutputStream(byteArrayOutput); objectOutput.writeObject(header); byte[] headerBytes = byteArrayOutput.toByteArray(); objectOutput.close(); byteArrayOutput.close(); bytes = new byte[headerBytes.length + frameBytes.length]; System.arraycopy(headerBytes, 0, bytes, 0, headerBytes.length); System.arraycopy(frameBytes, 0, bytes, headerBytes.length, frameBytes.length); } catch (IOException e) { e.printStackTrace(); } return bytes; } public VerificationStatus checkIntegrity(SPValidator validator) { return checkIntegrity(validator, null); } public VerificationStatus checkIntegrity(SPValidator validator, byte[] inputHash) { byte[] calculatedHash = null; if (inputHash == null) { validator.log("No input hash"); inputHash = new byte[0]; } validator.log(String.format("Frame ID: %s", byteArrayToHex(header.uniqueFrameID))); try { MessageDigest messageDigest = MessageDigest.getInstance(SPImage.DIGEST_ALGORITHM); messageDigest.update(imageData); messageDigest.update(inputHash); calculatedHash = messageDigest.digest(); if (verificationFactors != null) { validator.log("VerificationFactorData found"); for (Class<Verifier> v : verificationFactors) { VerificationFactorData factorData = verificationFactorData.get(v); if (factorData != null) { messageDigest.update(calculatedHash); messageDigest.update(factorData.getHash()); calculatedHash = messageDigest.digest(); } } } else { validator.log("No VerificationFactorData present"); } validator.log(String.format("Calculated hash: %s", byteArrayToHex(calculatedHash))); // validator.log("Signature TODO"); FrameInfo info = validator.lookupFrame(header.uniqueFrameID); if (info != null) { byte[] submittedHash = DatatypeConverter.parseBase64Binary(info.imageHash); validator.log(String.format("Retrieving frame hash: %s", byteArrayToHex(submittedHash))); validator.log(String.format("Frame hash submitted: %s", new Date(info.issueDate * 1000).toString())); boolean validationOK = Arrays.equals(calculatedHash, submittedHash); if (validationOK) { validator.log("Hash OK"); validator.log("Frame VERIFIED"); return VerificationStatus.OK; } else { validator.log("Hash verification FAILED."); return VerificationStatus.FAILED; } } else { validator.log("No frame record on server."); } } catch (NoSuchAlgorithmException e) { e.printStackTrace(); } return VerificationStatus.UNKNOWN; } private static String byteArrayToHex(byte[] bytes) { StringBuilder sb = new StringBuilder(); for (byte aByte : bytes) { sb.append(Integer.toHexString(aByte & 0xFF).toUpperCase()); } return sb.toString(); } @Override public String toString() { StringBuilder sb = new StringBuilder(); sb.append("SPImage \n"); sb.append(String.format("\n")); sb.append(String.format("Frame ID: %s\n", byteArrayToHex(header.uniqueFrameID))); sb.append(String.format("Frame hash: %s\n", byteArrayToHex(header.frameHash))); return sb.toString(); } public enum VerificationStatus { OK, FAILED, UNKNOWN } }