/* Copyright or (C) or Copr. GET / ENST, Telecom-Paris, Ludovic Apvrille * * ludovic.apvrille AT enst.fr * * This software is a computer program whose purpose is to allow the * edition of TURTLE analysis, design and deployment diagrams, to * allow the generation of RT-LOTOS or Java code from this diagram, * and at last to allow the analysis of formal validation traces * obtained from external tools, e.g. RTL from LAAS-CNRS and CADP * from INRIA Rhone-Alpes. * * This software is governed by the CeCILL license under French law and * abiding by the rules of distribution of free software. You can use, * modify and/ or redistribute the software under the terms of the CeCILL * license as circulated by CEA, CNRS and INRIA at the following URL * "http://www.cecill.info". * * As a counterpart to the access to the source code and rights to copy, * modify and redistribute granted by the license, users are provided only * with a limited warranty and the software's author, the holder of the * economic rights, and the successive licensors have only limited * liability. * * In this respect, the user's attention is drawn to the risks associated * with loading, using, modifying and/or developing or reproducing the * software by the user in light of its specific status of free software, * that may mean that it is complicated to manipulate, and that also * therefore means that it is reserved for developers and experienced * professionals having in-depth computer knowledge. Users are therefore * encouraged to load and test the software's suitability as regards their * requirements in conditions enabling the security of their systems and/or * data to be ensured and, more generally, to use and operate it in the * same conditions as regards security. * * The fact that you are presently reading this means that you have had * knowledge of the CeCILL license and that you accept its terms. */ package proverifspec; import avatartranslator.toproverif.AVATAR2ProVerif; import myutil.TraceManager; import ui.AvatarDesignPanel; import ui.TAttribute; import ui.avatarbd.AvatarBDBlock; import java.io.BufferedWriter; import java.io.IOException; import java.util.HashMap; import java.util.LinkedList; import java.util.regex.Matcher; import java.util.regex.Pattern; /** * Class ProVerifResultTrace * Creation: 22/05/2017 * @version 1.0 22/05/2017 * @author Florian LUGOU */ public class ProVerifResultTrace { private static Pattern tracePattern; private static Pattern blockNamePattern; private static Pattern attrPattern; private LinkedList<ProVerifResultTraceStep> trace; private StringBuilder buffer; private LinkedList<String> proverifProcess; private HashMap<String, Integer> attackerNamesMap; static { ProVerifResultTrace.attrPattern = Pattern.compile("\\b((\\w+?)" + AVATAR2ProVerif.ATTR_DELIM + ")?(\\w+?)(" + AVATAR2ProVerif.ATTR_DELIM + "[0-9_]+?)?(_\\d+)?\\b(\\[[^\\]]*\\])?"); ProVerifResultTrace.tracePattern = Pattern.compile("^\\d+\\. (.*)$"); ProVerifResultTrace.blockNamePattern = Pattern.compile("let \\(=sessionID,=call" + AVATAR2ProVerif.ATTR_DELIM + "(.+?)" + AVATAR2ProVerif.ATTR_DELIM + ".*"); } private class OutStep implements ProVerifResultTraceStep { private String from; private String to; private String message; private String channel; public OutStep(String from, String to, String message, String channel) { this.from = from; this.to = to; this.message = message; this.channel = channel; } public boolean messageEquals(String message) { return this.message.equals(message); } public void setTo(String to) { this.to = to; } public boolean isToAttacker() { return this.to.equals("Attacker"); } @Override public String describeAsString(AvatarDesignPanel adp) { return "MSG " + this.from + " -- " + this.channel + " --> " + this.to + " : " + ProVerifResultTrace.this.replaceAllAttributeNames(adp, this.message).replaceAll(",", ", "); } @Override public void describeAsSDTransaction(AvatarDesignPanel adp, BufferedWriter writer, int step) throws IOException { writer.write("#" + step + " time=0.000000000 block=" + this.from + " blockdestination=" + this.to + " type=synchro channel=" + this.channel + " params=\"" + ProVerifResultTrace.this.replaceAllAttributeNames(adp, this.message).replaceAll(",", ", ") + "\""); writer.newLine(); writer.flush(); } @Override public void describeAsTMLSDTransaction(BufferedWriter writer, int step) throws IOException { writer.write("#" + step + " time=0.000000000 block=" + this.from + " blockdestination=" + this.to + " type=synchro channel=" + this.channel + " params=\"" + this.message.replaceAll(",", ", ") + "\""); writer.newLine(); writer.flush(); } } private class EventStep implements ProVerifResultTraceStep { private String block; private String name; public EventStep (String block, String name) { this.block = block; this.name = name; } @Override public String describeAsString(AvatarDesignPanel adp) { return "EV " + this.block + "." + this.name; } @Override public void describeAsSDTransaction(AvatarDesignPanel adp, BufferedWriter writer, int step) throws IOException { writer.write("#" + step + " time=0.000000000 block=" + this.block + " type=state_entering state="+ this.name); writer.newLine(); writer.flush(); } @Override public void describeAsTMLSDTransaction(BufferedWriter writer, int step) throws IOException { writer.write("#" + step + " time=0.000000000 block=" + this.block + " type=state_entering state="+ this.name); writer.newLine(); writer.flush(); } } private class NewStep implements ProVerifResultTraceStep { private String name; public NewStep(String name) { this.name = name; } @Override public String describeAsString(AvatarDesignPanel adp) { return "NEW " + ProVerifResultTrace.this.replaceAttributeName(adp, this.name); } @Override public void describeAsSDTransaction(AvatarDesignPanel adp, BufferedWriter writer, int step) throws IOException { writer.write("#" + step + " time=0.000000000 block=Attacker type=function_call func=new parameters=" + ProVerifResultTrace.this.replaceAttributeName(adp, this.name)); writer.newLine(); writer.flush(); } @Override public void describeAsTMLSDTransaction(BufferedWriter writer, int step) throws IOException { writer.write("#" + step + " time=0.000000000 block=Attacker type=function_call func=new parameters=" + this.name); writer.newLine(); writer.flush(); } } public ProVerifResultTrace(LinkedList<String> proverifProcess) { this.proverifProcess = proverifProcess; this.trace = new LinkedList<ProVerifResultTraceStep> (); this.attackerNamesMap = new HashMap<String, Integer> (); this.buffer = null; } public LinkedList<ProVerifResultTraceStep> getTrace() { return this.trace; } public void addTraceStep(String str) { Matcher m = tracePattern.matcher(str); if (m.matches()) { this.finalizeStep(); this.buffer = new StringBuilder(); str = m.group(1); } this.buffer.append(str); } private String replaceAttributeName(AvatarDesignPanel adp, String str) { Matcher m = ProVerifResultTrace.attrPattern.matcher(str); if (m.matches()) { String part1 = m.group(2); String part2 = m.group(3); Integer s; if (part1 == null) { part1 = "Attacker"; s = this.attackerNamesMap.get(part1 + AVATAR2ProVerif.ATTR_DELIM + str); if (s == null) return str; } else { s = this.attackerNamesMap.get(str); if (s != null) { String [] spl = str.split(AVATAR2ProVerif.ATTR_DELIM, 3); part1 = spl[0]; part2 = spl[1]; } } String blockPart = "Attacker"; String attrPart = part2; if (!part1.equals("Attacker")) { // TODO: why is it just name and not FQN ? AvatarBDBlock block = adp.getAvatarBDPanel().getBlockFromOwnerName(part1.replaceAll("__", ".")); if (block == null) { TraceManager.addDev("[ERROR] Unknown block : " + part1); return null; } if (s == null) blockPart = block.getOwnerName(); String attrName[] = part2.split("__", 2); if (attrName.length >= 2) { TAttribute attr = block.getAttributeByName(attrName[0]); if (attr == null) { // TODO: can happen when library function ? TraceManager.addDev("[ERROR] Unknown attribute : " + part2 + " for block " + block.getOwnerName()); attrPart = part2.replaceAll("__", "."); } else { // TODO: is it possible that subtype is itself composed ? LinkedList<TAttribute> types = adp.getAvatarBDPanel().getAttributesOfDataType(attr.getTypeOther()); if (types.size() > 1) attrPart = attrName[0] + "." + attrName[1]; else attrPart = attrName[0]; } } } if (s != null && s.intValue() > 0) attrPart += "_" + s; return blockPart + "." + attrPart; } return str; } private String replaceAllAttributeNames(AvatarDesignPanel adp, String str) { Matcher m = ProVerifResultTrace.attrPattern.matcher(str); String result = ""; int lastEnd = 0; while (m.find(lastEnd)) { result += str.substring(lastEnd, m.start()); result += this.replaceAttributeName(adp, m.group(0)); lastEnd = m.end(); } result += str.substring(lastEnd); // Replace pk(...) Pattern p = Pattern.compile("pk\\(([a-zA-Z0-9_.]+)\\)"); m = p.matcher(result); lastEnd = 0; str = ""; while (m.find(lastEnd)) { String replaceBy = m.group(0); for (String pragma: adp.getModelPragmas()) { String parts[] = pragma.split("\\s+"); if (!parts[0].equals("#PrivatePublicKeys") || parts.length < 4) continue; if (m.group(1).equals(parts[1] + "." + parts[2])) { replaceBy = parts[1] + "." + parts[3]; break; } } str += result.substring(lastEnd, m.start()); str += replaceBy; lastEnd = m.end(); } str += result.substring(lastEnd); return str; } private String getBlockNameFromLine(int line) { for (int i=line; i>=0; i--) { Matcher m = blockNamePattern.matcher(this.proverifProcess.get(i)); if (m.matches()) { return m.group(1).replaceAll("__", "."); } } // This can happen when out(pk(...)) in no process due to pragma PrivatePublicKey return null; } private String consumePrecondition(String str) { try { Pattern p = Pattern.compile("The message (.+?) that the attacker may have by (.+?) may be received at input \\{(.+?)\\}\\.(.*)"); Matcher m = p.matcher(str); if (m.matches()) { String msgName = m.group(1); if (msgName.startsWith("chControlEnc") || msgName.startsWith("strong" + AVATAR2ProVerif.ATTR_DELIM) || msgName.startsWith("choice" + AVATAR2ProVerif.ATTR_DELIM)) return m.group(4); String channelName = ""; Matcher m2 = Pattern.compile(AVATAR2ProVerif.CH_ENCRYPT + ".+?__(.+?)\\((.*)\\)").matcher(msgName); if (m2.matches()) { channelName = m2.group(1); msgName = m2.group(2); } boolean foundAStep = false; for (ProVerifResultTraceStep step: this.trace) { if (step instanceof OutStep) { OutStep out = (OutStep) step; if (out.messageEquals(msgName) && out.isToAttacker()) { foundAStep = true; out.setTo(this.getBlockNameFromLine(Integer.parseInt(m.group(3)))); break; } // TODO: ProVerif does not output twice the out in case of weak authenticity violation... // It can only be seen in the trace reconstruction phase. } } if (!foundAStep) { this.trace.add(new OutStep("Attacker", this.getBlockNameFromLine(Integer.parseInt(m.group(3))), msgName, channelName)); } return m.group(4); } p = Pattern.compile("We have (.+?)\\.(.*)"); m = p.matcher(str); if (m.matches()) { return m.group(2); } p = Pattern.compile("The event (.+?)( \\(with environment .+?)? may be executed at \\{(.+?)\\}\\.(.*)"); m = p.matcher(str); if (m.matches()) { return m.group(4); } } catch (NumberFormatException e) { TraceManager.addDev("[ERROR] Parsing int"); } return null; } private void finalizeStep() { if (this.buffer == null) return; String str = this.buffer.toString(); String newStr = str; while (newStr != null) { str = newStr; newStr = this.consumePrecondition(newStr); } if (str.startsWith("By ")) { return; } try { Pattern p = Pattern.compile("(So t|T)he message (.*) may be sent (.*) at output \\{(\\d+)\\}.*"); Matcher m = p.matcher(str); if (m.matches()) { String msgName = m.group(2); if (msgName.startsWith("strong" + AVATAR2ProVerif.ATTR_DELIM) || msgName.startsWith("choice" + AVATAR2ProVerif.ATTR_DELIM) || msgName.startsWith("chControlEnc")) return; String blockName = this.getBlockNameFromLine(Integer.parseInt(m.group(4))); String channelName = ""; m = Pattern.compile(AVATAR2ProVerif.CH_ENCRYPT + ".+?__(.+?)\\((.*)\\)").matcher(msgName); if (m.matches()) { channelName = m.group(1); msgName = m.group(2); } if (blockName != null) this.trace.add(new OutStep(blockName, "Attacker", msgName, channelName)); return; } p = Pattern.compile("So event (.*) may be executed at \\{(\\d+)\\}( in session .+)?\\..*"); m = p.matcher(str); if (m.matches()) { String line = m.group(2); p = Pattern.compile("enteringState" + AVATAR2ProVerif.ATTR_DELIM + "[a-zA-Z0-9_]+" + AVATAR2ProVerif.ATTR_DELIM + "([a-zA-Z0-9_]+)"); m = p.matcher(m.group(1)); if (m.matches()) { this.trace.add(new EventStep(this.getBlockNameFromLine(Integer.parseInt(line)), m.group(1))); return; } return; } p = Pattern.compile("The attacker has some term (.*)\\.attacker\\(\\1\\)\\."); m = p.matcher(str); p = Pattern.compile("We assume as hypothesis thatattacker\\((\\w*)\\)\\."); Matcher m2 = p.matcher(str); if (m.matches() || m2.matches()) { String attrName; if (m.matches()) attrName = m.group(1); else attrName = m2.group(1); String attrName2 = attrName; if (attrName.startsWith("strong" + AVATAR2ProVerif.ATTR_DELIM) || attrName.startsWith("choice" + AVATAR2ProVerif.ATTR_DELIM)) return; m = ProVerifResultTrace.attrPattern.matcher(attrName); if (m.matches()) { String newName = m.group(3); if (!attrName.contains(AVATAR2ProVerif.ATTR_DELIM)) attrName = "Attacker" + AVATAR2ProVerif.ATTR_DELIM + attrName; int n = 0; for (String k: this.attackerNamesMap.keySet()) { if (newName.equals(k.split(AVATAR2ProVerif.ATTR_DELIM, 3)[1])) { if (this.attackerNamesMap.get(k) == 0) { this.attackerNamesMap.remove(k); this.attackerNamesMap.put(k, new Integer("1")); n = 2; break; } else { int nn = this.attackerNamesMap.get(k).intValue(); if (nn >= n) n = nn+1; } } } this.attackerNamesMap.put(attrName, new Integer(n)); this.trace.add(new NewStep(attrName2)); } return; } } catch (NumberFormatException e) { TraceManager.addDev("[ERROR] Parsing int"); } TraceManager.addDev("[DEBUG] unmatch: " + str); } public void finalize() { this.finalizeStep(); } }