package org.expeditee.encryption.io; import java.io.BufferedReader; import java.io.FileInputStream; import java.io.FileReader; import java.io.IOException; import java.io.InputStreamReader; import java.io.Reader; import java.lang.reflect.Method; import java.security.InvalidKeyException; import java.security.NoSuchAlgorithmException; import java.util.ArrayList; import java.util.Arrays; import java.util.Base64; import java.util.List; import java.util.function.BiConsumer; import java.util.function.Predicate; import javax.crypto.BadPaddingException; import javax.crypto.Cipher; import javax.crypto.IllegalBlockSizeException; import javax.crypto.NoSuchPaddingException; import javax.crypto.SecretKey; import javax.crypto.spec.SecretKeySpec; import org.expeditee.core.Point; import org.expeditee.encryption.CryptographyConstants; import org.expeditee.encryption.items.surrogates.EncryptionDetail; import org.expeditee.encryption.items.surrogates.Label; //import org.expeditee.encryption.items.surrogates.Label; import org.expeditee.encryption.items.surrogates.Label.LabelResult; import org.expeditee.encryption.items.surrogates.Label.LabelInfo; import org.expeditee.gui.Frame; import org.expeditee.gui.FrameIO; import org.expeditee.io.Conversion; import org.expeditee.io.DefaultFrameWriter; import org.expeditee.io.ExpReader; import org.expeditee.items.Constraint; import org.expeditee.items.Item; import org.expeditee.items.Text; import org.expeditee.settings.identity.secrets.KeyList; public class EncryptedExpReader extends ExpReader implements CryptographyConstants { private static final String ENCRYPTED_EXP_FLAG = "EncryptedExp"; private static final String labelProfile = "Profile"; private static final String labelNone = "None"; private SecretKey personalKey; private boolean accessDenied = false; private boolean _readingSurrogates; private static final Predicate endOfSection = s -> s.equals(EncryptedExpWriter.TERMINATOR + "") || s.equals(EncryptedExpWriter.TERMINATOR_WITH_CONTINUATION); public static boolean isEncryptedExpediteeFile(final String path) throws IOException { BufferedReader in = new BufferedReader(new InputStreamReader(new FileInputStream(path), "UTF-8")); String firstLine = in.readLine(); in.close(); if (firstLine == null) return false; return firstLine.startsWith(ENCRYPTED_EXP_FLAG); } public EncryptedExpReader(String frameName) { super(frameName); } public int getVersionEnc(String fullpath) { try { BufferedReader reader = new EncryptedLineReader(new BufferedReader(new FileReader(fullpath))); String next = ""; // First read the header lines until we get the version number while (reader.ready() && !(next = reader.readLine()).equals("Z")) { if (isValidLine(next)) { Character tag = getTag(next); String value = getValue(next); if (tag.equals('V')) { reader.close(); return Integer.parseInt(value); } } } reader.close(); } catch (Exception e) { } return -1; } @Override public Frame readFrame(final String fullPath) throws IOException { Reader in = new InputStreamReader(new FileInputStream(fullPath), "UTF-8"); return readFrame(new EncryptedLineReader(in)); } @Override public Frame readFrame(BufferedReader reader) throws IOException { if (accessDenied) { return null; } _readingSurrogates = false; _reader = reader; String next = ""; Frame newFrame = new Frame(); List delayedActions = new ArrayList(); newFrame.setName(_frameName); try { // First read all the header lines next = readTheHeader(newFrame); // Now read all the items next = readTheItems(newFrame, delayedActions); // Read the lines next = readTheLines(newFrame); // Read the constraints next = readTheConstraints(); if (next.equals(EncryptedExpWriter.TERMINATOR_WITH_CONTINUATION)) { // Read the surrogates _readingSurrogates = true; next = readTheItems(newFrame, delayedActions); _readingSurrogates = false; } for(DelayedAction action: delayedActions) { action.exec(); } // Read the stats next = readTheStats(newFrame); } catch (Exception e) { e.printStackTrace(); System.out.println("Error reading frame file line: " + next + " " + e.getMessage()); } _reader.close(); FrameIO.setSavedProperties(newFrame); newFrame.setChanged(false); return newFrame; } protected String readTheItems(Frame newFrame, List delayedActions) throws IOException { BiConsumer primaryAdd = (item, line) -> newFrame.addItem(item); BiConsumer surrogateAdd = (item, line) -> { int parentID = Integer.parseInt(line.split(" ")[1]); Item parent = newFrame.getItemWithID(parentID); newFrame.addToSurrogatesOnLoad(item, parent); }; if (_readingSurrogates) { return readLineAfterLine(true, surrogateAdd, delayedActions); } else { return readLineAfterLine(false, primaryAdd, delayedActions); } } @Override protected String readTheConstraints() throws IOException, Exception { String next = null; while (_reader.ready() && !endOfSection.test(next = _reader.readLine())) { if (isValidLine(next)) { Point idtype = separateValues(next.substring(2)); // The next line must be the endpoints if (!_reader.ready()) { throw new Exception("Unexpected end of file"); } next = _reader.readLine(); Point startend = separateValues(next.substring(2)); Item a = _linePoints.get(startend.getX()); Item b = _linePoints.get(startend.getY()); new Constraint(a, b, idtype.getX(), idtype.getY()); } } return next; } private String readLineAfterLine(boolean isSurrogate, BiConsumer storeResult, List delayedActions) throws IOException { String next = null; Item currentItem = null; while (_reader.ready() && !endOfSection.test(next = _reader.readLine())) { if (!isValidLine(next)) { continue; } String tag = getTagEnc(next); if (next.startsWith(DefaultFrameWriter.TYPE_AND_ID_STR + " ")) { currentItem = newItem(next); _linePoints.put(currentItem.getID(), currentItem); if (!isSurrogate) { storeResult.accept(currentItem, next); } EncryptionDetail unencryptedOnSave = new EncryptionDetail(EncryptionDetail.Type.UnencryptedOnSave); currentItem.setEncryptionDetailForTag(tag + "", unencryptedOnSave); } else if (next.startsWith("SurrogateFor")) { if (isSurrogate) { storeResult.accept(currentItem, next); } } else if (currentItem != null && actionShouldBeDelayed(tag.charAt(0))) { delayedActions.add(new DelayedAction(currentItem, next)); } else if (currentItem != null) { processBodyLine(currentItem, next); } else { System.err.println("Error while reading in frame (ExpReader): Found body line but no current item to apply it to."); } } return next; } @Override protected void processBodyLine(Item item, String line) { // separate the tag from the value String tag = getTagEnc(line); String value = getValue(line); boolean isEncryptedLine = isEncryptedLine(line); if (item.isSurrogate() && isEncryptedLine) { // Surrogates should never have encrypted body lines. return; } // Attempt to decrypt the line if necessary. if (isEncryptedLine) { LabelInfo res = Label.getLabel(item.getEncryptionLabel()); if (res.is(LabelResult.SuccessResolveLabelToKey)) { EncryptionDetail reencryptOnSave = new EncryptionDetail(EncryptionDetail.Type.ReencryptOnSave); item.setEncryptionDetailForTag(tag, reencryptOnSave); SecretKey key = new SecretKeySpec(res.key, SymmetricAlgorithm); byte[] decryptedBytes = DecryptSymmetric(Base64.getDecoder().decode(value), key); value = new String(decryptedBytes); } else { EncryptionDetail undecipheredValueOnSave = new EncryptionDetail(EncryptionDetail.Type.UseUndecipheredValueOnSave); undecipheredValueOnSave.setUndecipheredValue(getValue(line)); item.setEncryptionDetailForTag(tag, undecipheredValueOnSave); return; } } else { EncryptionDetail unencryptedOnSave = new EncryptionDetail(EncryptionDetail.Type.UnencryptedOnSave); item.setEncryptionDetailForTag(tag, unencryptedOnSave); if (item.isSurrogate()) { item.setTagNotInherited(tag); } } // Process the line Method toRun = tag.startsWith("_") ? _ItemTagsExt.get(tag) : _ItemTags.get(tag.charAt(0)); if (toRun == null) { System.out.println("Error accessing tag method: " + tag); } Object[] vals = Conversion.Convert(toRun, value); try { if (vals != null) { toRun.invoke(item, vals); } } catch (Exception e) { System.out.println("Error running tag method: " + tag); e.printStackTrace(); } } protected static String getValue(String line) { String[] split = line.split(" "); if (split.length >= 2) { return line.substring(split[0].length()).trim(); } else { return null; } } private static String getTagEnc(String line) { char charAtZero = line.charAt(0); if (charAtZero == '_') { return line.split(" ")[0]; } else { return charAtZero + ""; } } private static boolean isEncryptedLine(String line) { if (line.startsWith("S") || line.startsWith("_el")) { return false; } if (line.length() > 2) { return line.charAt(1) == 'E'; } else { return false; } } private static byte[] DecryptSymmetric(final byte[] toDecrypt, final SecretKey key) { try { final Cipher cipher = Cipher.getInstance(SymmetricAlgorithm + SymmetricAlgorithmParameters); cipher.init(Cipher.DECRYPT_MODE, key); final byte[] decryptedBytes = cipher.doFinal(toDecrypt); int indexOfZero = decryptedBytes.length - 1; for (int i = decryptedBytes.length - 1; i >= 0; i--) { if (decryptedBytes[i] != (byte) 0) { indexOfZero = i + 1; break; } } if (indexOfZero < 0) { return decryptedBytes; } else { return Arrays.copyOf(decryptedBytes, indexOfZero); } } catch (final NoSuchAlgorithmException | NoSuchPaddingException | InvalidKeyException | IllegalBlockSizeException | BadPaddingException e) { e.printStackTrace(); return null; } } private class EncryptedLineReader extends BufferedReader { private boolean noneMode = false; public EncryptedLineReader(Reader in) { super(in); } @Override /** * Reads a line from an encrypted exp file that uses an encryption specified by the first line of the file. * Returns that line to process, null if the currently logged in users doesn't own the appropriate key (access denied). */ public String readLine() throws IOException { String line = super.readLine(); if (line.isEmpty()) { return ""; } if (noneMode) { return line; } if (line.startsWith(ENCRYPTED_EXP_FLAG)) { String label = line.replace(ENCRYPTED_EXP_FLAG, ""); // if using Profile label, use personal key if (label.equals(labelProfile)) { Text text = KeyList.PersonalKey.get(); byte[] keyBytes = Base64.getDecoder().decode(text.getData().get(0)); personalKey = new SecretKeySpec(keyBytes, SymmetricAlgorithm); return readLine(); } else if (label.equals(labelNone)) { noneMode = true; return readLine(); } else { personalKey = resolveLabel(label); if (personalKey == null) { return null; } else { return readLine(); } } } // decrypt line and return result byte[] toDecrypt = Base64.getDecoder().decode(line); byte[] decrypted = DecryptSymmetric(toDecrypt, personalKey); if (decrypted == null) { accessDenied = true; return null; // access denied } else { String decryptedLine = new String(decrypted); if (decryptedLine.startsWith("Z")) { return decryptedLine.trim(); } else { return decryptedLine; } } } private SecretKeySpec resolveLabel(String label) { LabelInfo res = Label.getLabel(label); if (res.is(LabelResult.SuccessResolveLabelToKey)) { byte[] keyBytes = res.key; return new SecretKeySpec(keyBytes, SymmetricAlgorithm); } return null; } } }