diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 000000000..f9da31c8b --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,12 @@ +version: 2 +updates: +- package-ecosystem: maven + directory: "/" + schedule: + interval: weekly + time: "04:00" + open-pull-requests-limit: 10 + ignore: + - dependency-name: com.google.crypto.tink:tink + versions: + - 1.5.0 diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml new file mode 100644 index 000000000..42e4e1204 --- /dev/null +++ b/.github/workflows/deploy.yml @@ -0,0 +1,34 @@ +name: Deploy CryptoAnalysis + +on: [workflow_dispatch] + +jobs: + deployment: + runs-on: ubuntu-latest + name: CryptoAnalysis deployment + steps: + - name: Checkout source code + uses: actions/checkout@v3 + # Sets up Java version + - name: Set up Java + uses: actions/setup-java@v3 + with: + distribution: 'adopt' + java-package: 'jdk' + java-version: '8' + server-id: 'ossrh' # must match the serverId configured for the nexus-staging-maven-plugin + server-username: OSSRH_USERNAME # Env var that holds your OSSRH user name + server-password: OSSRH_PASSWORD # Env var that holds your OSSRH user pw + gpg-private-key: ${{ secrets.GPG_PRIVATE_KEY }} # Substituted with the value stored in the referenced secret + gpg-passphrase: SIGN_KEY_PASS # Env var that holds the key's passphrase + # Sets up Maven version + - name: Set up Maven + uses: stCarolas/setup-maven@v4.5 + with: + maven-version: 3.6.3 + - name: Build & Deploy CryptoAnalysis + run: mvn -B -U clean deploy -Pdeployment -DskipTests + env: + SIGN_KEY_PASS: ${{ secrets.GPG_PRIVATE_KEY_PASSPHRASE }} + OSSRH_USERNAME: ${{ secrets.SONATYPE_USER }} + OSSRH_PASSWORD: ${{ secrets.SONATYPE_PW }} \ No newline at end of file diff --git a/.gitignore b/.gitignore index 3946c6fd3..9d6c3ee1b 100644 --- a/.gitignore +++ b/.gitignore @@ -6,6 +6,7 @@ bin/ tmp/ *.tmp +*.temp *.bak *.swp *~.nib @@ -202,4 +203,5 @@ buildNumber.properties # End of https://www.toptal.com/developers/gitignore/api/java,maven,eclipse,intellij+all -.flattened-pom.xml \ No newline at end of file +.flattened-pom.xml +/shippable/testresults/ diff --git a/CryptoAnalysis-Android/pom.xml b/CryptoAnalysis-Android/pom.xml index f2bc6aa01..a3e27ea75 100644 --- a/CryptoAnalysis-Android/pom.xml +++ b/CryptoAnalysis-Android/pom.xml @@ -8,12 +8,12 @@ de.fraunhofer.iem CryptoAnalysis-Parent - ${revision} + 2.8.0 ../pom.xml - 2.7.1 + 2.12.0 @@ -69,53 +69,30 @@ CryptoAnalysis - de.tud.sse - soot-infoflow - ${flowDroidVersion} - - - de.tud.sse - soot-infoflow-android - ${flowDroidVersion} - - - de.tud.sse - soot-infoflow-cmd - ${flowDroidVersion} - + de.fraunhofer.sit.sse.flowdroid + soot-infoflow + ${flowDroidVersion} + + + de.fraunhofer.sit.sse.flowdroid + soot-infoflow-summaries + ${flowDroidVersion} + + + de.fraunhofer.sit.sse.flowdroid + soot-infoflow-android + ${flowDroidVersion} + - soot-snapshot - Soot snapshot repository - https://soot-build.cs.uni-paderborn.de/nexus/repository/soot-snapshot/ - default + ossrh + https://s01.oss.sonatype.org/content/repositories/snapshots/ - true - soot-release - Soot release repository - https://soot-build.cs.uni-paderborn.de/nexus/repository/soot-release/ - default + ossrh + https://s01.oss.sonatype.org/service/local/staging/deploy/maven2/ - - - - soot-snapshot - soot snapshots - https://soot-build.cs.uni-paderborn.de/nexus/repository/soot-snapshot/ - - false - - - - - soot-release - soot release - https://soot-build.cs.uni-paderborn.de/nexus/repository/soot-release/ - - - diff --git a/CryptoAnalysis/pom.xml b/CryptoAnalysis/pom.xml index 8b517c2d8..3ff6bae37 100644 --- a/CryptoAnalysis/pom.xml +++ b/CryptoAnalysis/pom.xml @@ -8,7 +8,7 @@ de.fraunhofer.iem CryptoAnalysis-Parent - ${revision} + 2.8.0 ../pom.xml @@ -30,7 +30,7 @@ org.apache.maven.plugins maven-dependency-plugin - 3.1.1 + 3.3.0 unpack @@ -49,7 +49,7 @@ org.apache.maven.plugins maven-dependency-plugin - 3.1.2 + 3.3.0 unpack @@ -125,7 +125,7 @@ junit junit - 4.13.1 + 4.13.2 @@ -176,7 +176,7 @@ maven-clean-plugin - 3.1.0 + 3.2.0 @@ -251,10 +251,17 @@ 4.13.2 test + + + com.google.inject + guice + 5.1.0 + org.apache.maven.plugins maven-invoker-plugin - 3.2.1 + 3.3.0 test @@ -284,12 +291,12 @@ com.google.code.gson gson - 2.9.0 + 2.10 de.darmstadt.tu.crossing.CrySL de.darmstadt.tu.crossing.CrySL - 2.0.1 + 2.0.2 org.eclipse.xtext @@ -300,13 +307,13 @@ org.eclipse.emf org.eclipse.emf.common - 2.25.0 + 2.26.0 org.eclipse.emf org.eclipse.emf.ecore - 2.27.0 + 2.28.0 @@ -318,7 +325,7 @@ com.fasterxml.jackson.core jackson-databind - 2.13.4.1 + 2.13.4.2 commons-io @@ -329,33 +336,12 @@ - soot-snapshot - Soot snapshot repository - https://soot-build.cs.uni-paderborn.de/nexus/repository/soot-snapshot/ - default + ossrh + https://s01.oss.sonatype.org/content/repositories/snapshots/ - true - soot-release - Soot release repository - https://soot-build.cs.uni-paderborn.de/nexus/repository/soot-release/ - default + ossrh + https://s01.oss.sonatype.org/service/local/staging/deploy/maven2/ - - - - soot-snapshot - soot snapshots - https://soot-build.cs.uni-paderborn.de/nexus/repository/soot-snapshot/ - - false - - - - soot-release - soot release - https://soot-build.cs.uni-paderborn.de/nexus/repository/soot-release/ - - diff --git a/CryptoAnalysis/src/main/java/crypto/HeadlessCryptoScanner.java b/CryptoAnalysis/src/main/java/crypto/HeadlessCryptoScanner.java index b689d6357..97858c7ec 100644 --- a/CryptoAnalysis/src/main/java/crypto/HeadlessCryptoScanner.java +++ b/CryptoAnalysis/src/main/java/crypto/HeadlessCryptoScanner.java @@ -5,6 +5,7 @@ import java.util.LinkedList; import java.util.List; import java.util.Map; +import java.util.Set; import java.util.concurrent.TimeUnit; import com.google.common.base.Stopwatch; import com.google.common.collect.Lists; @@ -27,8 +28,9 @@ import crypto.preanalysis.SeedFactory; import crypto.providerdetection.ProviderDetection; import crypto.reporting.CSVReporter; +import crypto.reporting.CSVSummaryReporter; import crypto.reporting.CommandLineReporter; -import crypto.reporting.ErrorMarkerListener; +import crypto.reporting.Reporter; import crypto.reporting.SARIFReporter; import crypto.reporting.TXTReporter; import crypto.rules.CrySLRule; @@ -162,10 +164,10 @@ private void analyse() { } public String toString() { - String s = "HeadllessCryptoScanner: \n"; - s += "\tSoftwareIdentifier: "+ softwareIdentifier() +"\n"; - s += "\tApplicationClassPath: "+ applicationClassPath() +"\n"; - s += "\tSootClassPath: "+ sootClassPath() +"\n\n"; + String s = "HeadlessCryptoScanner: \n"; + s += "\tSoftwareIdentifier: " + softwareIdentifier() + "\n"; + s += "\tApplicationClassPath: " + applicationClassPath() + "\n"; + s += "\tSootClassPath: " + sootClassPath() + "\n\n"; return s; } @@ -178,25 +180,52 @@ protected void internalTransform(String phaseName, Map options) BoomerangPretransformer.v().apply(); ObservableDynamicICFG observableDynamicICFG = new ObservableDynamicICFG(false); List rules = HeadlessCryptoScanner.rules; - ErrorMarkerListener fileReporter; - if(reportFormat()!= null) { - switch (reportFormat()) { - case SARIF: - fileReporter = new SARIFReporter(getOutputFolder(), rules); - break; - case CSV: - fileReporter = new CSVReporter(getOutputFolder(), softwareIdentifier(), rules, callGraphWatch.elapsed(TimeUnit.MILLISECONDS)); - break; - default: - fileReporter = new TXTReporter(getOutputFolder(), rules); + + long callgraphConstructionTime = callGraphWatch.elapsed(TimeUnit.MILLISECONDS); + + final CrySLResultsReporter reporter = new CrySLResultsReporter(); + Reporter fileReporter; + + Set formats = reportFormats(); + + if (formats.size() > 0) { + for (ReportFormat format : formats) { + switch (format) { + case CMD: + fileReporter = new CommandLineReporter(softwareIdentifier(), rules, callgraphConstructionTime, includeStatistics()); + reporter.addReportListener(fileReporter); + break; + case TXT: + fileReporter = new TXTReporter(getOutputFolder(), softwareIdentifier(), rules, callgraphConstructionTime, includeStatistics()); + reporter.addReportListener(fileReporter); + break; + case SARIF: + fileReporter = new SARIFReporter(getOutputFolder(), softwareIdentifier(), rules, callgraphConstructionTime, includeStatistics()); + reporter.addReportListener(fileReporter); + break; + case CSV: + fileReporter = new CSVReporter(getOutputFolder(), softwareIdentifier(), rules, callgraphConstructionTime, includeStatistics()); + reporter.addReportListener(fileReporter); + break; + case CSV_SUMMARY: + fileReporter = new CSVSummaryReporter(getOutputFolder(), softwareIdentifier(), rules, callgraphConstructionTime, includeStatistics()); + reporter.addReportListener(fileReporter); + break; + default: + fileReporter = new CommandLineReporter(softwareIdentifier(), rules, callgraphConstructionTime, includeStatistics()); + reporter.addReportListener(fileReporter); + } } + } else { + // if the --reportformat flag is not set or no format is specified, use the command line reporter as default + fileReporter = new CommandLineReporter(softwareIdentifier(), rules, callgraphConstructionTime, includeStatistics()); + reporter.addReportListener(fileReporter); } - else { - fileReporter = new CommandLineReporter(rules); + + if(getAdditionalListener() != null) { + reporter.addReportListener(getAdditionalListener()); } - final CrySLResultsReporter reporter = new CrySLResultsReporter(); - if(getAdditionalListener() != null) - reporter.addReportListener(getAdditionalListener()); + CryptoScanner scanner = new CryptoScanner() { @Override @@ -215,23 +244,25 @@ public Debugger debugger(IDEALSeedSolver if(getOutputFolder() == null) { LOGGER.error("The visualization requires the --reportDir option."); } - File vizFile = new File(getOutputFolder()+"/viz/ObjectId#"+seed.getObjectId()+".json"); + + File vizFile = new File(getOutputFolder() + "/viz/ObjectId#" + seed.getObjectId() + ".json"); vizFile.getParentFile().mkdirs(); + return new IDEVizDebugger<>(vizFile, icfg()); } return super.debugger(solver, seed); } }; - reporter.addReportListener(fileReporter); - if (providerDetection()) { ProviderDetection providerDetection = new ProviderDetection(); if(rulesetRootPath == null) { - rulesetRootPath = System.getProperty("user.dir")+File.separator+"src"+File.separator+"main"+File.separator+"resources"; + rulesetRootPath = System.getProperty("user.dir") + File.separator + "src" + File.separator + "main" + File.separator + "resources"; } + String detectedProvider = providerDetection.doAnalysis(observableDynamicICFG, rulesetRootPath); + if(detectedProvider != null) { rules.clear(); switch(settings.getRulesetPathType()) { @@ -366,15 +397,19 @@ protected boolean isPreAnalysis() { protected boolean enableVisualization(){ return settings.isVisualization(); } - - protected ReportFormat reportFormat() { - return settings.getReportFormat(); + + protected Set reportFormats() { + return settings.getReportFormats(); } protected boolean providerDetection() { return settings.isProviderDetectionAnalysis(); } + protected boolean includeStatistics() { + return settings.isIncludeStatistics(); + } + private static String pathToJCE() { // When whole program mode is disabled, the classpath misses jce.jar return System.getProperty("java.home") + File.separator + "lib" + File.separator + "jce.jar"; diff --git a/CryptoAnalysis/src/main/java/crypto/analysis/CryptoScannerSettings.java b/CryptoAnalysis/src/main/java/crypto/analysis/CryptoScannerSettings.java index 52d3b216f..2c60c3a09 100644 --- a/CryptoAnalysis/src/main/java/crypto/analysis/CryptoScannerSettings.java +++ b/CryptoAnalysis/src/main/java/crypto/analysis/CryptoScannerSettings.java @@ -1,5 +1,8 @@ package crypto.analysis; +import java.util.HashSet; +import java.util.Set; + import crypto.exceptions.CryptoAnalysisParserException; public class CryptoScannerSettings { @@ -12,10 +15,11 @@ public class CryptoScannerSettings { private String applicationPath = null; private String softwareIdentifier = ""; private String reportDirectory = null; - private ReportFormat reportFormat = null; + private Set reportFormats; private boolean preAnalysis; private boolean visualization; private boolean providerDetectionAnalysis; + private boolean includeStatistics; public CryptoScannerSettings() { setControlGraph(ControlGraph.CHA); @@ -23,6 +27,9 @@ public CryptoScannerSettings() { setPreAnalysis(false); setVisualization(false); setProviderDetectionAnalysis(false); + setIncludeStatistics(true); + + reportFormats = new HashSet<>(); } public ControlGraph getControlGraph() { @@ -88,13 +95,9 @@ public String getReportDirectory() { public void setReportDirectory(String reportDirectory) { this.reportDirectory = reportDirectory; } - - public ReportFormat getReportFormat() { - return reportFormat; - } - - public void setReportFormat(ReportFormat reportFormat) { - this.reportFormat = reportFormat; + + public Set getReportFormats() { + return reportFormats; } public boolean isPreAnalysis() { @@ -121,12 +124,22 @@ public void setProviderDetectionAnalysis(boolean providerDetectionAnalysis) { this.providerDetectionAnalysis = providerDetectionAnalysis; } + public boolean isIncludeStatistics() { + return includeStatistics; + } + + public void setIncludeStatistics(boolean includeStatistics) { + this.includeStatistics = includeStatistics; + } + public void parseSettingsFromCLI(String[] settings) throws CryptoAnalysisParserException { int mandatorySettings = 0; + if(settings == null) { showErrorMessage(); } - for(int i=0; i" will store + * the formats CMD, TXT and CSV and return the value 3). + * + * @param settings The command line input. + * @param position The position of the --reportformat flag in the command line input + * @return numFormats The number of specified formats. If a format is given twice, it is also + * counted twice. + * @throws CryptoAnalysisParserException if a reportformat value is not supported + */ + + private int parseReportFormatValues(String[] settings, int position) throws CryptoAnalysisParserException { + // settings should be the command line input and position the index of --reportFormat + int numFormats = 0; + + for (int i = position + 1; i < settings.length; i++) { + + // new argument is specified + if (settings[i].startsWith("-")) { break; - default: - throw new CryptoAnalysisParserException("Incorrect value "+reportFormatValue+" for --reportFormat option. " - + "Available options are: TXT, SARIF and CSV.\n"); + } + + String reportFormatValue = settings[i].toLowerCase(); + + switch (reportFormatValue) { + case "cmd": + reportFormats.add(ReportFormat.CMD); + break; + case "txt": + reportFormats.add(ReportFormat.TXT); + break; + case "sarif": + reportFormats.add(ReportFormat.SARIF); + break; + case "csv": + reportFormats.add(ReportFormat.CSV); + break; + case "csv_summary": + reportFormats.add(ReportFormat.CSV_SUMMARY); + break; + default: + throw new CryptoAnalysisParserException("Incorrect value " + reportFormatValue + " for --reportFormat option. " + + "Available options are: CMD, TXT, SARIF, CSV and CSV_SUMMARY.\n"); + } + + numFormats++; } + + return numFormats; } private static void showErrorMessage() throws CryptoAnalysisParserException { String errorMessage = "An error occurred while trying to parse the CLI arguments.\n" - +"The default command for running CryptoAnalysis is: \n"+ - "java -cp crypto.HeadlessCryptoScanner \\\r\n"+ - " --rulesDir \\\r\n" + - " --appPath \n"; + + "The default command for running CryptoAnalysis is: \n" + + "java -cp crypto.HeadlessCryptoScanner \\\r\n" + + " --rulesDir \\\r\n" + + " --appPath \n"; throw new CryptoAnalysisParserException(errorMessage); } private static void showErrorMessage(String arg) throws CryptoAnalysisParserException { - String errorMessage = "An error occured while trying to parse the CLI argument: "+arg+".\n" - +"The default command for running CryptoAnalysis is: \n" - + "java -cp crypto.HeadlessCryptoScanner \\\r\n" + - " --rulesDir \\\r\n" + - " --appPath \n" + String errorMessage = "An error occured while trying to parse the CLI argument: " + arg + ".\n" + + "The default command for running CryptoAnalysis is: \n" + + "java -cp crypto.HeadlessCryptoScanner \\\r\n" + + " --rulesDir \\\r\n" + + " --appPath \n" + "\nAdditional arguments that can be used are:\n" + "--cg \n" + "--sootPath \n" + "--identifier \n" + "--reportPath \n" - + "--reportFormat \n" + + "--reportFormat \n" + "--preanalysis (enables pre-analysis)\n" + "--visualization (enables the visualization, but also requires --reportPath option to be set)\n" - + "--providerDetection (enables provider detection analysis)\n"; + + "--providerDetection (enables provider detection analysis)\n" + + "--dstats (disable the statistic information in the reports)\n"; throw new CryptoAnalysisParserException(errorMessage); } diff --git a/CryptoAnalysis/src/main/java/crypto/cryslhandler/CrySLModelReader.java b/CryptoAnalysis/src/main/java/crypto/cryslhandler/CrySLModelReader.java index 5290720ec..9aa3ea19f 100644 --- a/CryptoAnalysis/src/main/java/crypto/cryslhandler/CrySLModelReader.java +++ b/CryptoAnalysis/src/main/java/crypto/cryslhandler/CrySLModelReader.java @@ -1,99 +1,43 @@ package crypto.cryslhandler; -import java.io.File; -import java.io.IOException; -import java.io.InputStream; -import java.net.MalformedURLException; -import java.net.URL; -import java.net.URLClassLoader; -import java.util.AbstractMap.SimpleEntry; -import java.util.ArrayList; -import java.util.Collections; -import java.util.HashMap; -import java.util.HashSet; -import java.util.List; -import java.util.Map; -import java.util.Map.Entry; -import java.util.Set; - import com.google.common.base.CharMatcher; import com.google.common.collect.Lists; import com.google.common.collect.Maps; -import com.google.common.io.Files; import com.google.inject.Injector; -import org.eclipse.emf.common.util.EList; -import org.eclipse.emf.common.util.URI; -import org.eclipse.emf.ecore.EObject; -import org.eclipse.emf.ecore.resource.Resource; -import org.eclipse.xtext.common.types.JvmExecutable; -import org.eclipse.xtext.common.types.JvmFormalParameter; -import org.eclipse.xtext.common.types.access.impl.ClasspathTypeProvider; -import org.eclipse.xtext.resource.XtextResource; -import org.eclipse.xtext.resource.XtextResourceSet; - - import crypto.exceptions.CryptoAnalysisException; -import com.google.common.base.CharMatcher; -import com.google.common.collect.Lists; -import com.google.common.collect.Maps; -import com.google.inject.Injector; import crypto.interfaces.ICrySLPredicateParameter; import crypto.interfaces.ISLConstraint; -import crypto.rules.CrySLArithmeticConstraint; +import crypto.rules.*; import crypto.rules.CrySLArithmeticConstraint.ArithOp; -import crypto.rules.CrySLComparisonConstraint; import crypto.rules.CrySLComparisonConstraint.CompOp; -import crypto.rules.CrySLCondPredicate; -import crypto.rules.CrySLConstraint; import crypto.rules.CrySLConstraint.LogOps; -import crypto.rules.CrySLForbiddenMethod; -import crypto.rules.CrySLMethod; -import crypto.rules.CrySLObject; -import crypto.rules.CrySLPredicate; -import crypto.rules.CrySLRule; -import crypto.rules.CrySLSplitter; -import crypto.rules.CrySLValueConstraint; -import crypto.rules.ParEqualsPredicate; -import crypto.rules.StateMachineGraph; -import crypto.rules.StateNode; -import crypto.rules.TransitionEdge; import de.darmstadt.tu.crossing.CrySLStandaloneSetup; import de.darmstadt.tu.crossing.constraints.CrySLArithmeticOperator; import de.darmstadt.tu.crossing.constraints.CrySLComparisonOperator; import de.darmstadt.tu.crossing.constraints.CrySLLogicalOperator; -import de.darmstadt.tu.crossing.crySL.ArithmeticExpression; -import de.darmstadt.tu.crossing.crySL.ArithmeticOperator; -import de.darmstadt.tu.crossing.crySL.ArrayElements; -import de.darmstadt.tu.crossing.crySL.ComparingOperator; -import de.darmstadt.tu.crossing.crySL.ComparisonExpression; -import de.darmstadt.tu.crossing.crySL.Constraint; -import de.darmstadt.tu.crossing.crySL.DestroysBlock; -import de.darmstadt.tu.crossing.crySL.Domainmodel; -import de.darmstadt.tu.crossing.crySL.EnsuresBlock; -import de.darmstadt.tu.crossing.crySL.Event; -import de.darmstadt.tu.crossing.crySL.Expression; -import de.darmstadt.tu.crossing.crySL.ForbMethod; -import de.darmstadt.tu.crossing.crySL.ForbiddenBlock; -import de.darmstadt.tu.crossing.crySL.Literal; -import de.darmstadt.tu.crossing.crySL.LiteralExpression; -import de.darmstadt.tu.crossing.crySL.LogicalImply; -import de.darmstadt.tu.crossing.crySL.LogicalOperator; import de.darmstadt.tu.crossing.crySL.Object; -import de.darmstadt.tu.crossing.crySL.ObjectDecl; -import de.darmstadt.tu.crossing.crySL.Order; -import de.darmstadt.tu.crossing.crySL.PreDefinedPredicates; -import de.darmstadt.tu.crossing.crySL.Pred; -import de.darmstadt.tu.crossing.crySL.PredLit; -import de.darmstadt.tu.crossing.crySL.ReqPred; -import de.darmstadt.tu.crossing.crySL.RequiredBlock; -import de.darmstadt.tu.crossing.crySL.SimpleOrder; -import de.darmstadt.tu.crossing.crySL.SuPar; -import de.darmstadt.tu.crossing.crySL.SuParList; -import de.darmstadt.tu.crossing.crySL.SuperType; -import de.darmstadt.tu.crossing.crySL.UnaryPreExpression; -import de.darmstadt.tu.crossing.crySL.UseBlock; +import de.darmstadt.tu.crossing.crySL.*; import de.darmstadt.tu.crossing.crySL.impl.DomainmodelImpl; import de.darmstadt.tu.crossing.crySL.impl.ObjectImpl; +import org.eclipse.emf.common.util.EList; +import org.eclipse.emf.common.util.URI; +import org.eclipse.emf.ecore.EObject; +import org.eclipse.emf.ecore.resource.Resource; +import org.eclipse.xtext.common.types.JvmExecutable; +import org.eclipse.xtext.common.types.JvmFormalParameter; +import org.eclipse.xtext.common.types.access.impl.ClasspathTypeProvider; +import org.eclipse.xtext.resource.XtextResource; +import org.eclipse.xtext.resource.XtextResourceSet; + +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.net.MalformedURLException; +import java.net.URL; +import java.net.URLClassLoader; +import java.util.AbstractMap.SimpleEntry; +import java.util.*; +import java.util.Map.Entry; public class CrySLModelReader { diff --git a/CryptoAnalysis/src/main/java/crypto/reporting/CSVReporter.java b/CryptoAnalysis/src/main/java/crypto/reporting/CSVReporter.java index dd56dd05f..a510a30b0 100644 --- a/CryptoAnalysis/src/main/java/crypto/reporting/CSVReporter.java +++ b/CryptoAnalysis/src/main/java/crypto/reporting/CSVReporter.java @@ -3,299 +3,140 @@ import java.io.File; import java.io.FileWriter; import java.io.IOException; -import java.util.Collection; +import java.util.ArrayList; import java.util.List; -import java.util.Map; import java.util.Map.Entry; -import java.util.Set; -import java.util.concurrent.TimeUnit; + import org.slf4j.Logger; import org.slf4j.LoggerFactory; + import com.google.common.base.Joiner; -import com.google.common.base.Stopwatch; -import com.google.common.collect.HashBasedTable; -import com.google.common.collect.Lists; -import com.google.common.collect.Maps; -import com.google.common.collect.Multimap; -import com.google.common.collect.Sets; -import com.google.common.collect.Table; -import com.google.common.collect.Table.Cell; -import boomerang.BackwardQuery; -import boomerang.Query; -import boomerang.jimple.Statement; -import boomerang.jimple.Val; -import boomerang.results.ForwardBoomerangResults; -import crypto.analysis.AnalysisSeedWithSpecification; -import crypto.analysis.EnsuredCrySLPredicate; -import crypto.analysis.IAnalysisSeed; + +import java.util.Set; + import crypto.analysis.errors.AbstractError; -import crypto.analysis.errors.ConstraintError; -import crypto.analysis.errors.ForbiddenMethodError; -import crypto.analysis.errors.HardCodedError; -import crypto.analysis.errors.ImpreciseValueExtractionError; -import crypto.analysis.errors.IncompleteOperationError; -import crypto.analysis.errors.NeverTypeOfError; -import crypto.analysis.errors.RequiredPredicateError; -import crypto.analysis.errors.TypestateError; -import crypto.extractparameter.CallSiteWithParamIndex; -import crypto.extractparameter.ExtractedValue; -import crypto.interfaces.ISLConstraint; -import crypto.rules.CrySLPredicate; import crypto.rules.CrySLRule; -import soot.MethodOrMethodContext; -import soot.Scene; +import soot.SootClass; import soot.SootMethod; -import soot.jimple.toolkits.callgraph.ReachableMethods; -import soot.util.queue.QueueReader; -import sync.pds.solver.nodes.Node; -import typestate.TransitionFunction; -public class CSVReporter extends ErrorMarkerListener { +/** + * This class extends the class {@link Reporter} by generating an analysis report and write it into a + * csv file. + * + * Compared to the {@link CSVSummaryReporter}, this reporter writes each error from the analysis into + * a single line. If the statistics are enabled, each line is extended by the corresponding statistic + * fields. Since the statistics are computed for the whole analysis, each value for the different fields + * are the same in all lines. + */ +public class CSVReporter extends Reporter { private static final Logger LOGGER = LoggerFactory.getLogger(CSVReporter.class); private static final String CSV_SEPARATOR = ";"; - private Set errors = Sets.newHashSet(); - private int seeds; - private List headers = Lists.newArrayList(); - private Map headersToValues = Maps.newHashMap(); - private List rules; - private Set dataflowReachableMethods = Sets.newHashSet(); - private Stopwatch analysisTime = Stopwatch.createUnstarted(); - - /** - * Path of directory of analysis reports - */ - private File reportDir; - /** - * name of the analysis report - */ private static final String REPORT_NAME = "CryptoAnalysis-Report.csv"; - /** - * the headers of CSV report + + private List headers; + private List contents; + + /** Headers for the errors. These headers are always part of the analysis report. */ + private enum Headers { + ErrorID, ErrorType, ViolatingClass, Class, Method, LineNumber, Statement, Message + } + + /** + * Headers for the statistics. These headers are only part of the analysis report, if + * the corresponding parameter in the constructor is set to true. */ - private enum Headers{ - SoftwareID,SeedObjectCount,CallGraphTime_ms,CryptoAnalysisTime_ms,CallGraphReachableMethods, - CallGraphReachableMethods_ActiveBodies,DataflowVisitedMethod + private enum StatisticHeaders { + SoftwareID, SeedObjectCount, CryptoAnalysisTime_ms, CallGraphTime_ms, CallGraphReachableMethods, + CallGraphReachableMethods_ActiveBodies, DataflowVisitedMethod } - + /** - * Creates {@link CSVReporter} a constructor with reportDir, softwareId, rules and callGraphConstructionTime as parameter + * Subclass of {@link Reporter}. Creates an instance of {@link CSVReporter}, which + * can be used to create a csv file containing the analysis report. * - * @param reportDir a {@link String} path giving the location of the report directory - * @param softwareId {@link Format} An identifier used to label output files in CSV report format - * @param rules {@link CrySLRule} the rules with which the project is analyzed - * @param callGraphConstructionTime {@link long} call graph construction time in ms + * @param reportDir A {@link String} path giving the location of the report directory. + * The reportPath should end without an ending file separator. + * @param softwareID A {@link String} for the analyzed software. + * @param rules A {@link List} of {@link CrySLRule} containing the rules the program is analyzed with. + * @param callgraphConstructionTime The time in milliseconds for the construction of the callgraph. + * @param includeStatistics Set this value to true, if the analysis report should contain some + * analysis statistics (e.g. the callgraph construction time). If this value is set + * to false, no statistics will be output. */ - public CSVReporter(String reportDir, String softwareId, List rules, long callGraphConstructionTime) { - this.reportDir = (reportDir != null ? new File(reportDir) : new File(System.getProperty("user.dir"))); - this.rules = rules; - ReachableMethods reachableMethods = Scene.v().getReachableMethods(); - QueueReader listener = reachableMethods.listener(); - Set visited = Sets.newHashSet(); - int callgraphReachableMethodsWithActiveBodies = 0; - while (listener.hasNext()) { - MethodOrMethodContext next = listener.next(); - visited.add(next.method()); - if (next.method().hasActiveBody()) { - callgraphReachableMethodsWithActiveBodies++; - } - } - int callgraphReachableMethods = visited.size(); - for(Headers h : Headers.values()){ + public CSVReporter(String reportDir, String softwareId, List rules, long callGraphConstructionTime, boolean includeStatistics) { + super((reportDir != null ? new File(reportDir) : new File(System.getProperty("user.dir"))), softwareId, rules, callGraphConstructionTime, includeStatistics); + + headers = new ArrayList<>(); + contents = new ArrayList<>(); + + for (Headers h : Headers.values()) { headers.add(h.toString()); } - put(Headers.SoftwareID,softwareId); - put(Headers.CallGraphTime_ms,callGraphConstructionTime); - put(Headers.CallGraphReachableMethods,callgraphReachableMethods); - put(Headers.CallGraphReachableMethods_ActiveBodies,callgraphReachableMethodsWithActiveBodies); - addDynamicHeader(ConstraintError.class.getSimpleName()); - addDynamicHeader(NeverTypeOfError.class.getSimpleName()); - addDynamicHeader(HardCodedError.class.getSimpleName()); - addDynamicHeader(TypestateError.class.getSimpleName()); - addDynamicHeader(RequiredPredicateError.class.getSimpleName()); - addDynamicHeader(IncompleteOperationError.class.getSimpleName()); - addDynamicHeader(ImpreciseValueExtractionError.class.getSimpleName()); - addDynamicHeader(ForbiddenMethodError.class.getSimpleName()); - } - - private void addDynamicHeader(String name) { - headers.add(name+"_sum"); - for(CrySLRule r : rules){ - headers.add(name+"_"+r.getClassName()); - } - } - - @Override - public void beforeAnalysis() { - analysisTime.start(); - } - - @Override - public void afterAnalysis() { - analysisTime.stop(); - put(Headers.DataflowVisitedMethod, dataflowReachableMethods.size()); - put(Headers.CryptoAnalysisTime_ms, analysisTime.elapsed(TimeUnit.MILLISECONDS)); - put(Headers.SeedObjectCount, seeds); - Table errorTable = HashBasedTable.create(); - for(AbstractError err : errors){ - Integer integer = errorTable.get(err.getClass(), err.getRule()); - if(integer == null){ - integer = 0; + if (includeStatistics()) { + for (StatisticHeaders h : StatisticHeaders.values()) { + headers.add(h.toString()); } - integer++; - errorTable.put(err.getClass(), err.getRule(),integer); } + } - - for(Cell c : errorTable.cellSet()){ - put(c.getRowKey().getSimpleName() + "_" + c.getColumnKey().getClassName(), c.getValue()); - } + @Override + public void handleAnalysisResults() { + int idCount = 0; - Map errorsAccumulated = Maps.newHashMap(); - for(Cell c : errorTable.cellSet()){ - Integer integer = errorsAccumulated.get(c.getRowKey()); - if(integer == null){ - integer = 0; + for (SootClass c : this.errorMarkers.rowKeySet()) { + String className = c.getName(); + + for (Entry> e : this.errorMarkers.row(c).entrySet()) { + String methodName = e.getKey().getSubSignature(); + + for (AbstractError marker : e.getValue()) { + String errorType = marker.getClass().getSimpleName(); + String violatingClass = marker.getRule().getClassName(); + String errorMessage = marker.toErrorMarkerString(); + int lineNumber = marker.getErrorLocation().getUnit().get().getJavaSourceStartLineNumber(); + String statement = marker.getErrorLocation().getUnit().get().toString(); + + String line = idCount + CSV_SEPARATOR + errorType + CSV_SEPARATOR + violatingClass + CSV_SEPARATOR + className + + CSV_SEPARATOR + methodName + CSV_SEPARATOR + lineNumber + CSV_SEPARATOR + statement + CSV_SEPARATOR + errorMessage; + + // Add the statistics to every single line of the report + if (includeStatistics()) { + line += CSV_SEPARATOR + getStatistics().getSoftwareID() + CSV_SEPARATOR + getStatistics().getSeedObjectCount() + CSV_SEPARATOR + + getStatistics().getAnalysisTime() + CSV_SEPARATOR + getStatistics().getCallgraphTime() + + CSV_SEPARATOR + getStatistics().getCallgraphReachableMethods() + CSV_SEPARATOR + getStatistics().getCallgraphReachableMethodsWithActiveBodies() + + CSV_SEPARATOR + getStatistics().getDataflowVisitedMethods(); + } + + contents.add(line); + + idCount++; + } } - integer += c.getValue(); - errorsAccumulated.put(c.getRowKey(),integer); - } - - for(Entry c : errorsAccumulated.entrySet()){ - put(c.getKey().getSimpleName() + "_sum", c.getValue()); } writeToFile(); } - + private void writeToFile() { try { - FileWriter writer = new FileWriter(reportDir + File.separator+ REPORT_NAME); + FileWriter writer = new FileWriter(getOutputFolder() + File.separator + REPORT_NAME); + + // write headers writer.write(Joiner.on(CSV_SEPARATOR).join(headers) + "\n"); - List line = Lists.newArrayList(); - for(String h : headers){ - String string = headersToValues.get(h); - if(string == null){ - string = ""; - } - line.add(string); + + // write errors line by line + for (String line : this.contents) { + writer.write(line + "\n"); } - writer.write(Joiner.on(CSV_SEPARATOR).join(line) + "\n"); - writer.write("\n"+SARIFConfig.ANALYSISTOOL_NAME_VALUE+"\n"); - String version = getClass().getPackage().getImplementationVersion(); - if(version == null) { - version = "Version is not known"; - } - writer.write(version); + writer.close(); - LOGGER.info("CSV Report generated to file : "+ reportDir.getAbsolutePath() + File.separator+ REPORT_NAME); + LOGGER.info("CSV Report generated to file : " + getOutputFolder().getAbsolutePath() + File.separator+ REPORT_NAME); } catch (IOException e) { - LOGGER.error("Could not write to " + reportDir.getAbsolutePath() + File.separator+ REPORT_NAME, e); + LOGGER.error("Could not write to " + getOutputFolder().getAbsolutePath() + File.separator + REPORT_NAME, e); } } - private void put(String key, Object val) { - if (!headers.contains(key)) { - LOGGER.error("Did not create a header to this value " + key); - } else { - if(val == null){ - LOGGER.info(key+" is null"); - } - else { - headersToValues.put(key, val.toString()); - } - } - } - private void put(Headers key, Object val) { - put(key.toString(),val); - } - - @Override - public void beforeConstraintCheck(AnalysisSeedWithSpecification analysisSeedWithSpecification) { - - } - - @Override - public void afterConstraintCheck(AnalysisSeedWithSpecification analysisSeedWithSpecification) { - - } - - @Override - public void beforePredicateCheck(AnalysisSeedWithSpecification analysisSeedWithSpecification) { - - } - - @Override - public void afterPredicateCheck(AnalysisSeedWithSpecification analysisSeedWithSpecification) { - - } - - @Override - public void seedStarted(IAnalysisSeed analysisSeedWithSpecification) { - // TODO Auto-generated method stub - - } - - @Override - public void boomerangQueryStarted(Query seed, BackwardQuery q) { - } - - @Override - public void boomerangQueryFinished(Query seed, BackwardQuery q) { - - } - - @Override - public void reportError(AbstractError error) { - errors.add(error); - } - - @Override - public void ensuredPredicates(Table> existingPredicates, - Table> expectedPredicates, - Table> missingPredicates) { - - } - - @Override - public void checkedConstraints(AnalysisSeedWithSpecification analysisSeedWithSpecification, - Collection relConstraints) { - } - - @Override - public void onSeedTimeout(Node seed) { - - } - - @Override - public void onSeedFinished(IAnalysisSeed seed, ForwardBoomerangResults forwardResults) { - dataflowReachableMethods.addAll(forwardResults.getStats().getCallVisitedMethods()); - } - - - @Override - public void collectedValues(AnalysisSeedWithSpecification seed, - Multimap collectedValues) { - - } - - @Override - public void discoveredSeed(IAnalysisSeed curr) { - seeds++; - } - - @Override - public void onSecureObjectFound(IAnalysisSeed analysisObject) { - // TODO Auto-generated method stub - - } - - @Override - public void addProgress(int processedSeeds, int workListsize) { - // TODO Auto-generated method stub - - } - } diff --git a/CryptoAnalysis/src/main/java/crypto/reporting/CSVSummaryReporter.java b/CryptoAnalysis/src/main/java/crypto/reporting/CSVSummaryReporter.java new file mode 100644 index 000000000..abb5b9422 --- /dev/null +++ b/CryptoAnalysis/src/main/java/crypto/reporting/CSVSummaryReporter.java @@ -0,0 +1,210 @@ +package crypto.reporting; + +import java.io.File; +import java.io.FileWriter; +import java.io.IOException; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Set; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import com.google.common.base.Joiner; +import com.google.common.collect.HashBasedTable; +import com.google.common.collect.Lists; +import com.google.common.collect.Maps; +import com.google.common.collect.Sets; +import com.google.common.collect.Table; +import com.google.common.collect.Table.Cell; +import crypto.analysis.errors.AbstractError; +import crypto.analysis.errors.ConstraintError; +import crypto.analysis.errors.ForbiddenMethodError; +import crypto.analysis.errors.HardCodedError; +import crypto.analysis.errors.ImpreciseValueExtractionError; +import crypto.analysis.errors.IncompleteOperationError; +import crypto.analysis.errors.NeverTypeOfError; +import crypto.analysis.errors.RequiredPredicateError; +import crypto.analysis.errors.TypestateError; +import crypto.rules.CrySLRule; + +/** + * This class extends the class {@link Reporter} by generating a summary of the analysis and write it into a + * csv file. Compared to the {@link CSVReporter} this reporter will not output any information about the concrete + * errors found in the analysis. The summary will only contain the number of error types and in which classes from + * the rule set the errors were found. + */ +public class CSVSummaryReporter extends Reporter { + + private static final Logger LOGGER = LoggerFactory.getLogger(CSVSummaryReporter.class); + + private static final String CSV_SEPARATOR = ";"; + private Set errors = Sets.newHashSet(); + private List headers = Lists.newArrayList(); + private Map headersToValues = Maps.newHashMap(); + + /** Name of the analysis report */ + private static final String REPORT_NAME = "CryptoAnalysis-Report-Summary.csv"; + + /** The headers of CSV report */ + private enum Headers { + SoftwareID, SeedObjectCount, CryptoAnalysisTime_ms, CallGraphTime_ms, CallGraphReachableMethods, + CallGraphReachableMethods_ActiveBodies, DataflowVisitedMethod + } + + /** + * Subclass of {@link Reporter}. Creates an instance of {@link CSVSummaryReporter}, which + * can be used to create a csv file containing a summary of the analysis. + * + * @param reportDir A {@link String} path giving the location of the report directory. + * The reportPath should end without an ending file separator. + * @param softwareID A {@link String} for the analyzed software. + * @param rules A {@link List} of {@link CrySLRule} containing the rules the program is analyzed with. + * @param callgraphConstructionTime The time in milliseconds for the construction of the callgraph. + * @param includeStatistics Set this value to true, if the analysis report should contain some + * analysis statistics (e.g. the callgraph construction time). If this value is set + * to false, no statistics will be output. + */ + public CSVSummaryReporter(String reportDir, String softwareId, List rules, long callGraphConstructionTime, boolean includeStatistics) { + super((reportDir != null ? new File(reportDir) : new File(System.getProperty("user.dir"))), softwareId, rules, callGraphConstructionTime, includeStatistics); + + // include statistics only if wanted + if (includeStatistics()) { + // Create headers for the statistics + for (Headers h : Headers.values()) { + headers.add(h.toString()); + } + + put(Headers.SoftwareID, getStatistics().getSoftwareID()); + put(Headers.CallGraphTime_ms, getStatistics().getCallgraphTime()); + put(Headers.CallGraphReachableMethods, getStatistics().getCallgraphReachableMethods()); + put(Headers.CallGraphReachableMethods_ActiveBodies, getStatistics().getCallgraphReachableMethodsWithActiveBodies()); + } + + // Create headers for the errors depending on the used set of rules + addDynamicHeader(ConstraintError.class.getSimpleName()); + addDynamicHeader(NeverTypeOfError.class.getSimpleName()); + addDynamicHeader(HardCodedError.class.getSimpleName()); + addDynamicHeader(TypestateError.class.getSimpleName()); + addDynamicHeader(RequiredPredicateError.class.getSimpleName()); + addDynamicHeader(IncompleteOperationError.class.getSimpleName()); + addDynamicHeader(ImpreciseValueExtractionError.class.getSimpleName()); + addDynamicHeader(ForbiddenMethodError.class.getSimpleName()); + } + + /** + * Create headers for all specified rules with the format _ + * (e.g. ConstraintError_java.security.AlgorithmParameterGenerator). + * + * @param name Name of the error class + */ + private void addDynamicHeader(String name) { + headers.add(name + "_sum"); + + for (CrySLRule r : getRules()) { + headers.add(name + "_" + r.getClassName()); + } + } + + private void put(Headers key, Object val) { + put(key.toString(), val); + } + + private void put(String key, Object val) { + if (!headers.contains(key)) { + LOGGER.error("Did not create a header to this value " + key); + } else { + if (val == null) { + LOGGER.info(key + " is null"); + } else { + headersToValues.put(key, val.toString()); + } + } + } + + @Override + public void handleAnalysisResults() { + if (includeStatistics()) { + put(Headers.DataflowVisitedMethod, getStatistics().getDataflowVisitedMethods()); + put(Headers.CryptoAnalysisTime_ms, getStatistics().getAnalysisTime()); + put(Headers.SeedObjectCount, getStatistics().getSeedObjectCount()); + } + + // Count the number of each error class + Table errorTable = HashBasedTable.create(); + + for (AbstractError err : errors) { + Integer integer = errorTable.get(err.getClass(), err.getRule()); + + if(integer == null) { + integer = 0; + } + + integer++; + errorTable.put(err.getClass(), err.getRule(), integer); + } + + // Set the corresponding error headers to the number of occurred errors + for (Cell c : errorTable.cellSet()) { + put(c.getRowKey().getSimpleName() + "_" + c.getColumnKey().getClassName(), c.getValue()); + } + + Map errorsAccumulated = Maps.newHashMap(); + + for (Cell c : errorTable.cellSet()) { + Integer integer = errorsAccumulated.get(c.getRowKey()); + + if(integer == null) { + integer = 0; + } + + integer += c.getValue(); + errorsAccumulated.put(c.getRowKey(), integer); + } + + for (Entry c : errorsAccumulated.entrySet()) { + put(c.getKey().getSimpleName() + "_sum", c.getValue()); + } + + writeToFile(); + } + + private void writeToFile() { + try { + FileWriter writer = new FileWriter(getOutputFolder() + File.separator + REPORT_NAME); + writer.write(Joiner.on(CSV_SEPARATOR).join(headers) + "\n"); + + List line = Lists.newArrayList(); + + for (String h : headers) { + String string = headersToValues.get(h); + + if (string == null) { + string = ""; + } + + line.add(string); + } + + writer.write(Joiner.on(CSV_SEPARATOR).join(line) + "\n"); + writer.write("\n" + SARIFConfig.ANALYSISTOOL_NAME_VALUE + "\n"); + + String version = getClass().getPackage().getImplementationVersion(); + + if (version == null) { + version = "Version is not known"; + } + + writer.write(version); + writer.close(); + LOGGER.info("CSV Report generated to file : " + getOutputFolder().getAbsolutePath() + File.separator + REPORT_NAME); + } catch (IOException e) { + LOGGER.error("Could not write to " + getOutputFolder().getAbsolutePath() + File.separator + REPORT_NAME, e); + } + } + + @Override + public void reportError(AbstractError error) { + errors.add(error); + } + +} diff --git a/CryptoAnalysis/src/main/java/crypto/reporting/CommandLineReporter.java b/CryptoAnalysis/src/main/java/crypto/reporting/CommandLineReporter.java index 027545219..bad0f2d54 100644 --- a/CryptoAnalysis/src/main/java/crypto/reporting/CommandLineReporter.java +++ b/CryptoAnalysis/src/main/java/crypto/reporting/CommandLineReporter.java @@ -1,52 +1,52 @@ package crypto.reporting; import java.io.File; -import java.util.Collection; -import java.util.HashSet; import java.util.List; - -import crypto.analysis.IAnalysisSeed; import crypto.rules.CrySLRule; -public class CommandLineReporter extends ErrorMarkerListener { +/** + * This class extends the class {@link Reporter} by generating an analysis report and print it to the command line. + */ +public class CommandLineReporter extends Reporter { - private File outputFolder; - private List rules; - private Collection objects = new HashSet<>(); - - /** - * The analysis report - */ + /**The analysis report */ private String analysisReport; - /** - * Creates {@link CommandLineReporter} a constructor with reportDir and rules as parameter + * Subclass of {@link Reporter}. Creates an instance of {@link CommandLineReporter} with reportDir and rules as parameter * * @param reportDir a {@link String} path giving the location of the report directory * @param rules {@link CrySLRule} the rules with which the project is analyzed */ public CommandLineReporter(String reportDir, List rules) { - this.outputFolder = (reportDir != null ? new File(reportDir) : null); - this.rules = rules; + super((reportDir != null ? new File(reportDir) : null), "", rules, -1, false); } /** - * Creates {@link CommandLineReporter} a constructor with reportDir and rules as parameter + * Subclass of {@link Reporter}. Creates an instance of {@link CommandLineReporter}, which + * can be used to print an analysis report to stdout. * - * @param rules {@link CrySLRule} the rules with which the project is analyzed + * @param reportDir A {@link String} path giving the location of the report directory. + * The reportPath should end without an ending file separator. + * @param softwareID A {@link String} for the analyzed software. + * @param rules A {@link List} of {@link CrySLRule} containing the rules the program is analyzed with. + * @param callgraphConstructionTime The time in milliseconds for the construction of the callgraph. + * @param includeStatistics Set this value to true, if the analysis report should contain some + * analysis statistics (e.g. the callgraph construction time). If this value is set + * to false, no statistics will be output. */ - public CommandLineReporter(List rules) { - this.rules = rules; + public CommandLineReporter(String softwareID, List rules, long callgraphConstructionTime, boolean includeStatistics) { + super(null, softwareID, rules, callgraphConstructionTime, includeStatistics); } @Override - public void discoveredSeed(IAnalysisSeed object) { - this.objects.add(object); - } - @Override - public void afterAnalysis() { - this.analysisReport = ReporterHelper.generateReport(this.rules, this.objects, this.secureObjects, this.errorMarkers, this.errorMarkerCount); + public void handleAnalysisResults() { + if (includeStatistics()) { + this.analysisReport = ReporterHelper.generateReport(getRules(), getObjects(), this.secureObjects, this.errorMarkers, this.errorMarkerCount, getStatistics()); + } else { + this.analysisReport = ReporterHelper.generateReport(getRules(), getObjects(), this.secureObjects, this.errorMarkers, this.errorMarkerCount, null); + } + System.out.println(analysisReport); } } diff --git a/CryptoAnalysis/src/main/java/crypto/reporting/ReportStatistics.java b/CryptoAnalysis/src/main/java/crypto/reporting/ReportStatistics.java new file mode 100644 index 000000000..e014c5f4c --- /dev/null +++ b/CryptoAnalysis/src/main/java/crypto/reporting/ReportStatistics.java @@ -0,0 +1,97 @@ +package crypto.reporting; + +/** + * This class is used by the class {@link Reporter} to store all statistics, which are relevant for the analysis. + * + * Currently the following statistics are supported: + * - softwareID: Identifier of the analyzed software. This value can be set by using the --identifier flag. + * - seedObjectCount: Number of seed objects. + * - analysisTime: The time in milliseconds for the actual analysis (e.g. without the initialization of the analysis + * and the construction of the callgraph) + * - callgraphTime: The time in milliseconds to construct the callgraph. + * - callgraphReachableMethods: The number of reachable methods in the callgraph. + * - callgraphReachableMethodsWithActiveBodies: The number of reachable methods with active bodies in the callgraph. + * - dataflowVisitedMethods: The number of visited methods in the dataflows. + */ +public class ReportStatistics { + + private String softwareID; + private int seedObjectCount; + private long analysisTime; + private long callgraphTime; + private int callgraphReachableMethods; + private int callgraphReachableMethodsWithActiveBodies; + private int dataflowVisitedMethods; + + /** + * Creates an instance to store all relevant statistics for an analysis. The softwareID is initialized with + * an empty string and all numeric variables are initialized with -1. The corresponding set methods should be + * used to update the statistic values. + */ + public ReportStatistics() { + this.softwareID = ""; + this.seedObjectCount = -1; + this.analysisTime = -1; + this.callgraphTime = -1; + this.callgraphReachableMethods = -1; + this.callgraphReachableMethodsWithActiveBodies = -1; + this.dataflowVisitedMethods = -1; + } + + public void setSoftwareID(String softwareID) { + this.softwareID = softwareID; + } + + public String getSoftwareID() { + return softwareID; + } + + public void setSeedObjectCount(int seedObjectCount) { + this.seedObjectCount = seedObjectCount; + } + + public int getSeedObjectCount() { + return seedObjectCount; + } + + public void setAnalysisTime(long analysisTime) { + this.analysisTime = analysisTime; + } + + public long getAnalysisTime() { + return analysisTime; + } + + public void setCallgraphTime(long callgraphTime) { + this.callgraphTime = callgraphTime; + } + + public long getCallgraphTime() { + return callgraphTime; + } + + public void setCallgraphReachableMethods(int callgraphReachableMethods) { + this.callgraphReachableMethods = callgraphReachableMethods; + } + + public int getCallgraphReachableMethods() { + return callgraphReachableMethods; + } + + public void setCallgraphReachableMethodsWithActiveBodies(int callgraphReachableMethodsWithActiveBodies) { + this.callgraphReachableMethodsWithActiveBodies = callgraphReachableMethodsWithActiveBodies; + } + + public int getCallgraphReachableMethodsWithActiveBodies() { + return callgraphReachableMethodsWithActiveBodies; + } + + public void setDataflowVisitedMethods(int dataflowVisitedMethods) { + this.dataflowVisitedMethods = dataflowVisitedMethods; + } + + public int getDataflowVisitedMethods() { + return dataflowVisitedMethods; + } + +} diff --git a/CryptoAnalysis/src/main/java/crypto/reporting/Reporter.java b/CryptoAnalysis/src/main/java/crypto/reporting/Reporter.java new file mode 100644 index 000000000..adb25eeed --- /dev/null +++ b/CryptoAnalysis/src/main/java/crypto/reporting/Reporter.java @@ -0,0 +1,143 @@ +package crypto.reporting; + +import java.io.File; +import java.util.Collection; +import java.util.HashSet; +import java.util.List; +import java.util.Set; +import java.util.concurrent.TimeUnit; + +import com.google.common.base.Stopwatch; + +import boomerang.results.ForwardBoomerangResults; +import crypto.analysis.IAnalysisSeed; +import crypto.rules.CrySLRule; +import soot.MethodOrMethodContext; +import soot.Scene; +import soot.SootMethod; +import soot.jimple.toolkits.callgraph.ReachableMethods; +import soot.util.queue.QueueReader; +import typestate.TransitionFunction; + +/** + * Superclass for all reporters. + * + * This class is used to define and implement the basic parts, which all reporter should be able to support. This includes + * the computation of all statistics for the analysis and the basic logic for methods defined in the {@link ICrySLResultsListener}. + * + * This class is abstract. Subclasses have to call the constructor and overwrite the method handleAnalysisResults(), which is called + * after the analysis is finished. + */ +public abstract class Reporter extends ErrorMarkerListener { + + private File outputFolder; + private List rules; + private boolean includeStatistics; + + /** An instance of {@link ReportStatistics} to store all relevant analysis statistics */ + protected final ReportStatistics statistics = new ReportStatistics(); + + /** The stopwatch to measure to time for the actual analysis */ + protected final Stopwatch analysisWatch = Stopwatch.createUnstarted(); + + /** A {@link Collection} to store and count all analyzed objects */ + protected final Collection objects = new HashSet<>(); + + /** A {@link Set} to store and count all reachable methods in the dataflow */ + protected final Set dataflowReachableMethods = new HashSet<>(); + + /** + * The constructor to initialize all attributes. Since this class is abstract, all subclasses + * have to call this constructor. + * + * @param reportDir A {@link String} path giving the location of the report directory. + * The reportPath should end without an ending file separator. + * @param softwareID A {@link String} for the analyzed software. + * @param rules A {@link List} of {@link CrySLRule} containing the rules the program is analyzed with. + * @param callgraphConstructionTime The time in milliseconds for the construction of the callgraph. + * @param includeStatistics Set this value to true, if the analysis report should contain some + * analysis statistics (e.g. the callgraph construction time). If this value is set + * to false, no statistics will be output. + */ + public Reporter(File outputFolder, String softwareID, List rules, long callgraphConstructionTime, boolean includeStatistics) { + this.outputFolder = outputFolder; + this.rules = rules; + this.includeStatistics = includeStatistics; + + this.statistics.setSoftwareID(softwareID); + this.statistics.setCallgraphTime(callgraphConstructionTime); + + // Compute reachable methods and visited methods + ReachableMethods reachableMethods = Scene.v().getReachableMethods(); + QueueReader listener = reachableMethods.listener(); + Set visited = new HashSet<>(); + + int callgraphReachableMethodsWithActiveBodies = 0; + + while (listener.hasNext()) { + MethodOrMethodContext next = listener.next(); + visited.add(next.method()); + + if (next.method().hasActiveBody()) { + callgraphReachableMethodsWithActiveBodies++; + } + } + + this.statistics.setCallgraphReachableMethods(visited.size()); + this.statistics.setCallgraphReachableMethodsWithActiveBodies(callgraphReachableMethodsWithActiveBodies); + } + + public File getOutputFolder() { + return outputFolder; + } + + public List getRules() { + return rules; + } + + public boolean includeStatistics() { + return includeStatistics; + } + + public ReportStatistics getStatistics() { + return statistics; + } + + public Collection getObjects() { + return objects; + } + + @Override + public void beforeAnalysis() { + this.analysisWatch.start(); + } + + @Override + public void discoveredSeed(IAnalysisSeed object) { + this.objects.add(object); + } + + @Override + public void onSeedFinished(IAnalysisSeed seed, ForwardBoomerangResults forwardResults) { + this.dataflowReachableMethods.addAll(forwardResults.getStats().getCallVisitedMethods()); + } + + @Override + public void afterAnalysis() { + this.analysisWatch.stop(); + + this.statistics.setSeedObjectCount(this.objects.size()); + this.statistics.setAnalysisTime(this.analysisWatch.elapsed(TimeUnit.MILLISECONDS)); + this.statistics.setDataflowVisitedMethods(this.dataflowReachableMethods.size()); + + handleAnalysisResults(); + } + + /** + * This method is called after the analysis is finished and all statistics have been computed. A subclass + * can override this method to extend the actions after the analysis, e.g. creating an analysis report + * and write it into a file. + */ + public abstract void handleAnalysisResults(); + +} diff --git a/CryptoAnalysis/src/main/java/crypto/reporting/ReporterHelper.java b/CryptoAnalysis/src/main/java/crypto/reporting/ReporterHelper.java index ae29a60e0..f7db834ea 100644 --- a/CryptoAnalysis/src/main/java/crypto/reporting/ReporterHelper.java +++ b/CryptoAnalysis/src/main/java/crypto/reporting/ReporterHelper.java @@ -15,21 +15,29 @@ import soot.SootClass; import soot.SootMethod; -public class ReporterHelper{ +/** + * This class is used to generate a report as a {@link String} for multiple other classes. This class is + * used by the {@link CommandLineReporter} and {@link TXTReporter}. + */ +public class ReporterHelper { - /** Generates analysis report content for {@link CommandLineReporter} CommandLineReporter and {@link TXTReporter} TXTReporter - * @param rules a {@link List} with {@link CrySLRule} rules - * @param objects a{@link Collection} with {@link IAnalysisSeed} objects - * @param secureObjects a {@link List} with {@link IAnalysisSeed} secureObjects - * @param errorMarkers a {@link Table} containing {@link SootClass},{@link SootMethod} - * and a {@link Set} of {@link AbstractError} of the errors found during analysis - * @param errorMarkerCount a {@link Map} containing {@link Class} class of error and - * {@link Integer} number of errors - * @return report {@link String} of the analysis + /** Generates an analysis report content for the {@link CommandLineReporter} and {@link TXTReporter}. + * + * @param rules A {@link List} with {@link CrySLRule} rules, which were used in the analysis + * @param objects A {@link Collection} with {@link IAnalysisSeed} objects + * @param secureObjects A {@link List} with {@link IAnalysisSeed} secureObjects + * @param errorMarkers A {@link Table} containing {@link SootClass}, {@link SootMethod} + * and a {@link Set} of {@link AbstractError} of the errors found during analysis + * @param errorMarkerCount A {@link Map} containing {@link Class} class of error and + * {@link Integer} number of errors + * @param statistics An instance of the class {@link ReportStatistics}, which holds all relevant statistics + * for the analysis. If no statistics should be included in the analysis, the value null + * should be passed. + * @return report The formatted analysis report as {@link String} */ public static String generateReport(List rules, Collection objects, List secureObjects, Table> errorMarkers, - Map errorMarkerCount){ + Map errorMarkerCount, ReportStatistics statistics) { String report = ""; report += "Ruleset: \n"; @@ -50,37 +58,60 @@ public static String generateReport(List rules, Collection> e : errorMarkers.row(c).entrySet()) { report += String.format("\n\t in Method: %s\n", e.getKey().getSubSignature()); + for (AbstractError marker : e.getValue()) { - report += String.format("\t\t%s violating CrySL rule for %s", marker.getClass().getSimpleName() ,marker.getRule().getClassName()); + report += String.format("\t\t%s violating CrySL rule for %s", marker.getClass().getSimpleName(), marker.getRule().getClassName()); + if(marker instanceof ErrorWithObjectAllocation) { report += String.format(" (on Object #%s)\n", ((ErrorWithObjectAllocation) marker).getObjectLocation().getObjectId()); } else { report += "\n"; } + report += String.format("\t\t\t%s\n", marker.toErrorMarkerString()); - report += String.format("\t\t\tat statement: %s\n\n", marker.getErrorLocation().getUnit().get()); + report += String.format("\t\t\tat statement: %s\n", marker.getErrorLocation().getUnit().get()); + report += String.format("\t\t\tat line: %d\n\n", marker.getErrorLocation().getUnit().get().getJavaSourceStartLineNumber()); } } + report += "\n"; } + report += "======================= CryptoAnalysis Summary ==========================\n"; report += String.format("\tNumber of CrySL rules: %s\n", rules.size()); report += String.format("\tNumber of Objects Analyzed: %s\n", objects.size()); - if(errorMarkers.rowKeySet().isEmpty()){ + + if (errorMarkers.rowKeySet().isEmpty()) { report += "No violation of any of the rules found.\n"; - } else{ + } else { report += "\n\tCryptoAnalysis found the following violations. For details see description above.\n"; - for(Entry e : errorMarkerCount.entrySet()){ - report += String.format("\t%s: %s\n", e.getKey().getSimpleName(),e.getValue()); + + for (Entry e : errorMarkerCount.entrySet()) { + report += String.format("\t%s: %s\n", e.getKey().getSimpleName(), e.getValue()); } } - report += "====================================================================="; + + // Only include the analysis statistics, if the passed instance is not null + if (statistics != null) { + report += "\n\tAdditional analysis statistics:\n"; + report += String.format("\t\tSoftwareID: %s\n", statistics.getSoftwareID()); + report += String.format("\t\tSeedObjectCount: %d\n", statistics.getSeedObjectCount()); + report += String.format("\t\tCryptoAnalysisTime (in ms): %d\n", statistics.getAnalysisTime()); + report += String.format("\t\tCallgraphConstructionTime (in ms): %d\n", statistics.getCallgraphTime()); + report += String.format("\t\tCallgraphReachableMethods: %d\n", statistics.getCallgraphReachableMethods()); + report += String.format("\t\tCallgraphReachableMethodsWithActiveBodies: %d\n", statistics.getCallgraphReachableMethodsWithActiveBodies()); + report += String.format("\t\tDataflowVisitedMethods: %d\n", statistics.getDataflowVisitedMethods()); + } + + report += "========================================================================="; + return report; } diff --git a/CryptoAnalysis/src/main/java/crypto/reporting/SARIFConfig.java b/CryptoAnalysis/src/main/java/crypto/reporting/SARIFConfig.java index 6f9c6f7a9..fddc170a2 100644 --- a/CryptoAnalysis/src/main/java/crypto/reporting/SARIFConfig.java +++ b/CryptoAnalysis/src/main/java/crypto/reporting/SARIFConfig.java @@ -1,6 +1,7 @@ package crypto.reporting; public class SARIFConfig { + public static final String VERSION = "version"; public static final String SARIF_VERSION = "sarifVersion"; public static final String SARIF_VERSION_NUMBER = "2.0.0"; @@ -35,8 +36,9 @@ public class SARIFConfig { public static final String URI_KEY = "uri"; public static final String REGION_KEY = "region"; public static final String START_LINE_KEY = "startLine"; + public static final String METHOD_KEY = "method"; + public static final String STATEMENT_KEY = "statement"; public static final String FULLY_QUALIFIED_LOGICAL_NAME_KEY = "fullyQualifiedLogicalName"; -// public static final String public static final String RESOURCES_KEY = "resources"; public static final String RULES_KEY = "rules"; @@ -63,4 +65,14 @@ public class SARIFConfig { public static final String INSTANCE_OF_ERROR_KEY = "InstanceOfError"; public static final String INSTANCE_OF_ERROR_VALUE = "Reported when a value was found to not be of a certain instance."; + // keys for the statistics + public static final String STATISTICS_KEY = "statistics"; + public static final String SOFTWAREID_KEY = "SoftwareID"; + public static final String SEEDOBJECTCOUNT_KEY = "SeedObjectCount"; + public static final String ANALYSISTIME_KEY = "CryptoAnalysisTime"; + public static final String CALLGRAPHTIME_KEY = "CallgraphConstructionTime"; + public static final String CALLGRAPHREACHABLEMETHODS_KEY = "CallgraphReachableMethods"; + public static final String CALLGRAPGREACHABLEMETHODSWITHACTIVEBODIES_KEY = "CallgraphRechableMethodsWithActiveBodies"; + public static final String DATAFLOWVISITEDMETHODS_KEY = "DataflowVisitedMethods"; + } diff --git a/CryptoAnalysis/src/main/java/crypto/reporting/SARIFHelper.java b/CryptoAnalysis/src/main/java/crypto/reporting/SARIFHelper.java index 4459466cb..ac52628a1 100644 --- a/CryptoAnalysis/src/main/java/crypto/reporting/SARIFHelper.java +++ b/CryptoAnalysis/src/main/java/crypto/reporting/SARIFHelper.java @@ -49,17 +49,21 @@ private JSONObject getRuns() { public JSONObject getToolInfo() { JSONObject tool = new JSONObject(); + tool.put(SARIFConfig.ANALYSISTOOL_NAME_KEY, SARIFConfig.ANALYSISTOOL_NAME_VALUE); tool.put(SARIFConfig.VERSION, getClass().getPackage().getImplementationVersion()); - tool.put(SARIFConfig.SEMANTIC_VERSION_KEY,getClass().getPackage().getImplementationVersion()); + tool.put(SARIFConfig.SEMANTIC_VERSION_KEY, getClass().getPackage().getImplementationVersion()); tool.put(SARIFConfig.LANGUAGE_KEY, SARIFConfig.LANGUAGE_VALUE); + return tool; } public JSONObject getMessage(String text, String richText) { JSONObject message = new JSONObject(); + message.put(SARIFConfig.TEXT_KEY, text); message.put(SARIFConfig.RICH_TEXT_KEY, richText); + return message; } @@ -67,27 +71,48 @@ public String getFileName(SootClass c) { return sourceLocater == null ? c.getName().replace(".", "/") + ".java" : sourceLocater.getAbsolutePath(c); } - public JSONArray getLocations(SootClass c, String methodName, int lineNumber) { + public JSONArray getLocations(SootClass c, String methodName, int lineNumber, String method, String statement) { JSONArray locations = new JSONArray(); JSONObject location = new JSONObject(); - JSONObject startLine = new JSONObject(); - startLine.put(SARIFConfig.START_LINE_KEY, lineNumber); + JSONObject region = new JSONObject(); + region.put(SARIFConfig.START_LINE_KEY, lineNumber); + region.put(SARIFConfig.METHOD_KEY, method); + region.put(SARIFConfig.STATEMENT_KEY, statement); + JSONObject uri = new JSONObject(); uri.put(SARIFConfig.URI_KEY, getFileName(c)); + JSONObject physicalLocation = new JSONObject(); physicalLocation.put(SARIFConfig.FILE_LOCATION_KEY, uri); - physicalLocation.put(SARIFConfig.REGION_KEY, startLine); + physicalLocation.put(SARIFConfig.REGION_KEY, region); location.put(SARIFConfig.PHYSICAL_LOCATION_KEY, physicalLocation); + String fullyQualifiedLogicalName = c.getName().replace(".", "::") + "::" + methodName; location.put(SARIFConfig.FULLY_QUALIFIED_LOGICAL_NAME_KEY, fullyQualifiedLogicalName); locations.add(location); + return locations; } public String getRuleDescription(String ruleId) { return this.rulesMap.get(ruleId); } + + public JSONObject getStatisticsInfo(ReportStatistics statistics) { + JSONObject statisticField = new JSONObject(); + + statisticField.put(SARIFConfig.SOFTWAREID_KEY, statistics.getSoftwareID()); + statisticField.put(SARIFConfig.SEEDOBJECTCOUNT_KEY, statistics.getSeedObjectCount()); + statisticField.put(SARIFConfig.ANALYSISTIME_KEY, statistics.getAnalysisTime()); + statisticField.put(SARIFConfig.CALLGRAPHTIME_KEY, statistics.getCallgraphTime()); + statisticField.put(SARIFConfig.CALLGRAPHREACHABLEMETHODS_KEY, statistics.getCallgraphReachableMethods()); + statisticField.put(SARIFConfig.CALLGRAPGREACHABLEMETHODSWITHACTIVEBODIES_KEY, statistics.getCallgraphReachableMethodsWithActiveBodies()); + statisticField.put(SARIFConfig.DATAFLOWVISITEDMETHODS_KEY, statistics.getDataflowVisitedMethods()); + + return statisticField; + } + } diff --git a/CryptoAnalysis/src/main/java/crypto/reporting/SARIFReporter.java b/CryptoAnalysis/src/main/java/crypto/reporting/SARIFReporter.java index e141fe84f..a17384fa9 100644 --- a/CryptoAnalysis/src/main/java/crypto/reporting/SARIFReporter.java +++ b/CryptoAnalysis/src/main/java/crypto/reporting/SARIFReporter.java @@ -3,9 +3,7 @@ import java.io.File; import java.nio.file.Paths; import java.io.IOException; -import java.util.Collection; import java.util.HashMap; -import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Map.Entry; @@ -20,7 +18,6 @@ import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.ObjectWriter; -import crypto.analysis.IAnalysisSeed; import crypto.analysis.errors.AbstractError; import crypto.rules.CrySLRule; import soot.SootClass; @@ -28,41 +25,44 @@ /** - * This class creates {@link SARIFReporter} a constructor with reportDir and rules as parameter - * - * @param reportDir a {@link String} path giving the location of the report directory - * @param rules {@link CrySLRule} the rules with which the project is analyzed + * This class extends the class {@link Reporter} by generating an analysis report and write it into a + * JSON file in the SARIF format. */ - - @SuppressWarnings("unchecked") -public class SARIFReporter extends ErrorMarkerListener { +public class SARIFReporter extends Reporter { private static final Logger LOGGER = LoggerFactory.getLogger(SARIFReporter.class); - private File outputFolder; - // private List rules; - private Collection objects = new HashSet<>(); - private JSONObject files = new JSONObject(), resources = new JSONObject(), rules = new JSONObject(); + private JSONObject files = new JSONObject(); + private JSONObject resources = new JSONObject(); + private JSONObject rules = new JSONObject(); private JSONArray results = new JSONArray(); private SARIFHelper sarifHelper; private Map errorCountMap; - /** - * name of the analysis report - */ + /** name of the analysis report */ private static final String REPORT_NAME = "CryptoAnalysis-Report.json"; - public SARIFReporter(String reportDir, List rules) { - this.outputFolder = (reportDir != null ? new File(reportDir) : new File(System.getProperty("user.dir"))); + /** + * Subclass of {@link Reporter}. Creates an instance of {@link SARIFReporter}, which + * can be used to create a json file containing the analysis report in the SARIF format. + * + * @param reportDir A {@link String} path giving the location of the report directory. + * The reportPath should end without an ending file separator. + * @param softwareID A {@link String} for the analyzed software. + * @param rules A {@link List} of {@link CrySLRule} containing the rules the program is analyzed with. + * @param callgraphConstructionTime The time in milliseconds for the construction of the callgraph. + * @param includeStatistics Set this value to true, if the analysis report should contain some + * analysis statistics (e.g. the callgraph construction time). If this value is set + * to false, no statistics will be output. + */ + public SARIFReporter(String reportDir, String softwareId, List rules, long callgraphConstructionTime, boolean includeStatistics) { + super((reportDir != null ? new File(reportDir) : new File(System.getProperty("user.dir"))), softwareId, rules, callgraphConstructionTime, includeStatistics); + this.sarifHelper = new SARIFHelper(); this.errorCountMap = new HashMap(); initializeMap(); } - public SARIFReporter(String string, List rules, SourceCodeLocater sourceLocater) { - this(string, rules); - this.sarifHelper = new SARIFHelper(sourceLocater); - } private void initializeMap() { this.errorCountMap.put(SARIFConfig.CONSTRAINT_ERROR_KEY, 0); this.errorCountMap.put(SARIFConfig.NEVER_TYPE_OF_ERROR_KEY, 0); @@ -84,53 +84,63 @@ private void addFile(SootClass c) { private String addRules(String errorType) { String finalErrorType = errorType; + if (this.rules.containsKey(errorType)) { int count = this.errorCountMap.get(errorType); count++; finalErrorType = errorType.concat("-".concat(Integer.toString(count))); this.errorCountMap.put(errorType, count); } + JSONObject ruleInfo = new JSONObject(); JSONObject fullDescription = new JSONObject(); + fullDescription.put(SARIFConfig.TEXT_KEY, this.sarifHelper.getRuleDescription(errorType)); ruleInfo.put(SARIFConfig.RULES_ID_KEY, errorType); ruleInfo.put(SARIFConfig.FULL_DESCRIPTION_KEY, fullDescription); this.rules.put(finalErrorType, ruleInfo); + return finalErrorType; } - private void addResults(String errorType, SootClass c, String methodName, int lineNumber, String text, + private void addResults(String errorType, SootClass c, String methodName, int lineNumber, String method, String statement, String text, String richText) { JSONObject result = new JSONObject(); String finalErrorType = addRules(errorType); + result.put(SARIFConfig.RULE_ID_KEY, finalErrorType); result.put(SARIFConfig.MESSAGE_KEY, this.sarifHelper.getMessage(text, richText)); - result.put(SARIFConfig.LOCATIONS_KEY, this.sarifHelper.getLocations(c, methodName, lineNumber)); + result.put(SARIFConfig.LOCATIONS_KEY, this.sarifHelper.getLocations(c, methodName, lineNumber, method, statement)); this.results.add(result); } private JSONObject makeSARIF() { this.resources.put(SARIFConfig.RULES_KEY, this.rules); + JSONObject sarif = new JSONObject(); sarif.put(SARIFConfig.SARIF_VERSION, SARIFConfig.SARIF_VERSION_NUMBER); + JSONArray runs = new JSONArray(); JSONObject run = new JSONObject(); + run.put(SARIFConfig.TOOL_KEY, this.sarifHelper.getToolInfo()); + + if (includeStatistics()) { + run.put(SARIFConfig.STATISTICS_KEY, this.sarifHelper.getStatisticsInfo(getStatistics())); + } + run.put(SARIFConfig.FILES_KEY, this.files); run.put(SARIFConfig.RESULTS_KEY, this.results); run.put(SARIFConfig.RESOURCES_KEY, this.resources); runs.add(run); + sarif.put(SARIFConfig.RUNS_KEY, runs); + return sarif; } @Override - public void discoveredSeed(IAnalysisSeed object) { - this.objects.add(object); - } - - @Override - public void afterAnalysis() { + public void handleAnalysisResults() { for (SootClass c : this.errorMarkers.rowKeySet()) { addFile(c); @@ -141,18 +151,24 @@ public void afterAnalysis() { marker.getClass().getSimpleName(), marker.getRule().getClassName()); String text = String.format("%s.", marker.toErrorMarkerString()); int lineNumber = marker.getErrorLocation().getUnit().get().getJavaSourceStartLineNumber(); - this.addResults(errorType, c, e.getKey().getName(), lineNumber, text, richText); + String method = e.getKey().getSubSignature(); + String statement = marker.getErrorLocation().getUnit().get().toString(); + this.addResults(errorType, c, e.getKey().getName(), lineNumber, method, statement, text, richText); } } } + JSONObject sarif = makeSARIF(); + try { ObjectMapper mapper = new ObjectMapper(); ObjectWriter writer = mapper.writer(new DefaultPrettyPrinter()); - writer.writeValue(Paths.get(outputFolder + File.separator + REPORT_NAME).toFile(), sarif); - LOGGER.info("SARIF Report generated to file : "+ outputFolder + File.separator + REPORT_NAME); + writer.writeValue(Paths.get(getOutputFolder() + File.separator + REPORT_NAME).toFile(), sarif); + + LOGGER.info("SARIF Report generated to file : " + getOutputFolder() + File.separator + REPORT_NAME); } catch (IOException e) { - LOGGER.error("Could not write to file: "+outputFolder.getAbsolutePath() + File.separator+ REPORT_NAME, e); + LOGGER.error("Could not write to file: " + getOutputFolder().getAbsolutePath() + File.separator + REPORT_NAME, e); } } + } diff --git a/CryptoAnalysis/src/main/java/crypto/reporting/TXTReporter.java b/CryptoAnalysis/src/main/java/crypto/reporting/TXTReporter.java index 116984fc3..4a8d71996 100644 --- a/CryptoAnalysis/src/main/java/crypto/reporting/TXTReporter.java +++ b/CryptoAnalysis/src/main/java/crypto/reporting/TXTReporter.java @@ -6,67 +6,72 @@ import java.io.IOException; import java.io.OutputStreamWriter; import java.io.PrintWriter; -import java.util.Collection; -import java.util.HashSet; import java.util.List; + import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import crypto.analysis.IAnalysisSeed; import crypto.rules.CrySLRule; import soot.Printer; import soot.SootClass; import soot.util.EscapedWriter; -public class TXTReporter extends ErrorMarkerListener{ +/** + * This class extends the class {@link Reporter} by generating a report and writing it into a text file. + */ +public class TXTReporter extends Reporter { - private File outputFolder; - private List rules; - private Collection objects = new HashSet<>(); private static final Logger LOG = LoggerFactory.getLogger(TXTReporter.class); - /** - * The report of the analysis - */ + + /** The report of the analysis */ private String analysisReport; - /** - * name of the analysis report - */ + + /** name of the analysis report */ private static final String REPORT_NAME = "CryptoAnalysis-Report.txt"; /** - * Creates {@link TXTReporter} a constructor with reportDir and rules as parameter + * Subclass of {@link Reporter}. Creates an instance of {@link TXTReporter}, which + * can be used to create a text file containing the analysis report. * - * @param reportDir a {@link String} path giving the location of the report directory - * @param rules {@link CrySLRule} the rules with which the project is analyzed + * @param reportDir A {@link String} path giving the location of the report directory. + * The reportPath should end without an ending file separator. + * @param softwareID A {@link String} for the analyzed software. + * @param rules A {@link List} of {@link CrySLRule} containing the rules the program is analyzed with. + * @param callgraphConstructionTime The time in milliseconds for the construction of the callgraph. + * @param includeStatistics Set this value to true, if the analysis report should contain some + * analysis statistics (e.g. the callgraph construction time). If this value is set + * to false, no statistics will be output. */ - public TXTReporter(String reportDir, List rules) { - this.outputFolder = (reportDir != null ? new File(reportDir) : new File(System.getProperty("user.dir"))); - this.rules = rules; + public TXTReporter(String reportDir, String softwareID, List rules, long callgraphConstructionTime, boolean includeStatistics) { + super((reportDir != null ? new File(reportDir) : new File(System.getProperty("user.dir"))), softwareID, rules, callgraphConstructionTime, includeStatistics); } @Override - public void discoveredSeed(IAnalysisSeed object) { - this.objects.add(object); - } - @Override - public void afterAnalysis() { - this.analysisReport = ReporterHelper.generateReport(rules, objects, this.secureObjects, this.errorMarkers, this.errorMarkerCount); + public void handleAnalysisResults() { + if (includeStatistics()) { + this.analysisReport = ReporterHelper.generateReport(getRules(), getObjects(), this.secureObjects, this.errorMarkers, this.errorMarkerCount, getStatistics()); + } else { + this.analysisReport = ReporterHelper.generateReport(getRules(), getObjects(), this.secureObjects, this.errorMarkers, this.errorMarkerCount, null); + } try { - FileWriter writer = new FileWriter(outputFolder + File.separator + REPORT_NAME); + FileWriter writer = new FileWriter(getOutputFolder() + File.separator + REPORT_NAME); writer.write(this.analysisReport); writer.close(); + for (SootClass c : this.errorMarkers.rowKeySet()) { - FileOutputStream streamOut = new FileOutputStream(new File(outputFolder + File.separator +c.toString()+".jimple")); + FileOutputStream streamOut = new FileOutputStream(new File(getOutputFolder() + File.separator + c.toString() + ".jimple")); PrintWriter writerOut = new PrintWriter(new EscapedWriter(new OutputStreamWriter(streamOut))); Printer.v().printTo(c, writerOut); + writerOut.flush(); streamOut.close(); writerOut.close(); } - LOG.info("Text Report generated to file : "+ outputFolder.getAbsolutePath() + File.separator + REPORT_NAME); + + LOG.info("Text Report generated to file : "+ getOutputFolder().getAbsolutePath() + File.separator + REPORT_NAME); } catch (IOException e) { - LOG.error("Could not write to file " + outputFolder.getAbsolutePath() + File.separator+ REPORT_NAME, e); + LOG.error("Could not write to file " + getOutputFolder().getAbsolutePath() + File.separator+ REPORT_NAME, e); } - } + } diff --git a/CryptoAnalysis/src/test/java/tests/headless/AbstractHeadlessTest.java b/CryptoAnalysis/src/test/java/tests/headless/AbstractHeadlessTest.java index e5697e9d1..f93efb5ba 100644 --- a/CryptoAnalysis/src/test/java/tests/headless/AbstractHeadlessTest.java +++ b/CryptoAnalysis/src/test/java/tests/headless/AbstractHeadlessTest.java @@ -2,6 +2,7 @@ import java.io.File; import java.util.Collection; +import java.util.HashSet; import java.util.List; import java.util.Set; import org.junit.Before; @@ -56,10 +57,21 @@ public abstract class AbstractHeadlessTest { private static boolean PROVIDER_DETECTION = true; private CrySLAnalysisListener errorCountingAnalysisListener; private Table, Integer> errorMarkerCountPerErrorTypeAndMethod = HashBasedTable.create(); - private static ReportFormat reportFormat = null; + private static Set reportFormats = new HashSet<>(); public static void setReportFormat(ReportFormat reportFormat) { - AbstractHeadlessTest.reportFormat = reportFormat; + // use this method to add exactly one report format + AbstractHeadlessTest.reportFormats.clear(); + AbstractHeadlessTest.reportFormats.add(reportFormat); + } + + public static void setReportFormat(ReportFormat ...formats) { + // use this method to add multiple report formats + AbstractHeadlessTest.reportFormats.clear(); + + for (ReportFormat format : formats) { + AbstractHeadlessTest.reportFormats.add(format); + } } public static void setVISUALIZATION(boolean vISUALIZATION) { @@ -129,8 +141,8 @@ protected boolean providerDetection() { } @Override - protected ReportFormat reportFormat(){ - return VISUALIZATION ? reportFormat : null; + protected Set reportFormats(){ + return VISUALIZATION ? reportFormats : new HashSet<>(); } }; return scanner; diff --git a/CryptoAnalysis/src/test/java/tests/headless/ReportFormatTest.java b/CryptoAnalysis/src/test/java/tests/headless/ReportFormatTest.java index 4b7a82dd2..607c8afb8 100644 --- a/CryptoAnalysis/src/test/java/tests/headless/ReportFormatTest.java +++ b/CryptoAnalysis/src/test/java/tests/headless/ReportFormatTest.java @@ -13,9 +13,10 @@ public class ReportFormatTest extends AbstractHeadlessTest{ private static final String rootPath = "cognicrypt-output/"; - private static final String txtReportPath = rootPath+"CryptoAnalysis-Report.txt"; - private static final String csvReportPath = rootPath+"CryptoAnalysis-Report.csv"; - private static final String sarifReportPath = rootPath+"CryptoAnalysis-Report.json"; + private static final String txtReportPath = rootPath + "CryptoAnalysis-Report.txt"; + private static final String csvReportPath = rootPath + "CryptoAnalysis-Report.csv"; + private static final String csvSummaryReportPath = rootPath + "CryptoAnalysis-Report-Summary.csv"; + private static final String sarifReportPath = rootPath + "CryptoAnalysis-Report.json"; @Test public void TXTReportCreationTest() { @@ -47,6 +48,24 @@ public void CSVReportCreationTest() { Assert.assertTrue(report.exists()); } + @Test + public void CSVSummaryCreationTest() { + File report = new File(csvSummaryReportPath); + + if (report.exists()) { + report.delete(); + } + + String mavenProjectPath = new File("../CryptoAnalysisTargets/ReportFormatExample").getAbsolutePath(); + MavenProject mavenProject = createAndCompile(mavenProjectPath); + setReportFormat(ReportFormat.CSV_SUMMARY); + setVISUALIZATION(true); + + HeadlessCryptoScanner scanner = createScanner(mavenProject); + scanner.exec(); + Assert.assertTrue(report.exists()); + } + @Test public void SARIFReportCreationTest() { File report = new File(sarifReportPath); @@ -62,6 +81,47 @@ public void SARIFReportCreationTest() { Assert.assertTrue(report.exists()); } + @Test + public void multipleFormatsCreationTest() { + File txtReport = new File(txtReportPath); + + if (txtReport.exists()) { + txtReport.delete(); + } + + File csvReport = new File(csvReportPath); + + if (csvReport.exists()) { + csvReport.delete(); + } + + File csvSummaryReport = new File(csvSummaryReportPath); + + if (csvSummaryReport.exists()) { + csvSummaryReport.delete(); + } + + File sarifReport = new File(sarifReportPath); + + if (sarifReport.exists()) { + sarifReport.delete(); + } + + String mavenProjectPath = new File("../CryptoAnalysisTargets/ReportFormatExample").getAbsolutePath(); + MavenProject mavenProject = createAndCompile(mavenProjectPath); + + setReportFormat(ReportFormat.CMD, ReportFormat.TXT, ReportFormat.CSV, ReportFormat.CSV_SUMMARY, ReportFormat.SARIF); + setVISUALIZATION(true); + + HeadlessCryptoScanner scanner = createScanner(mavenProject); + scanner.exec(); + + Assert.assertTrue(txtReport.exists()); + Assert.assertTrue(csvReport.exists()); + Assert.assertTrue(csvSummaryReport.exists()); + Assert.assertTrue(sarifReport.exists()); + } + @After public void tearDown() { try { diff --git a/README.md b/README.md index 1cfac9e75..71194dd57 100644 --- a/README.md +++ b/README.md @@ -49,18 +49,19 @@ Other additional arguments that can be used are as follows: --cg (possible values are CHA, SPARK, SPARKLIB) --sootPath --identifier ---reportPath ---reportFormat (possible values are TXT, SARIF, CSV) +--reportPath +--reportFormat (possible values are CMD, TXT, SARIF, CSV, CSV_SUMMARY) --preanalysis (enables pre-analysis) --visualization (enables the visualization, but also requires --reportPath option to be set) --providerDetection (enables provider detection analysis) +--dstats (disable the output of the analysis statistics in the reports) ``` Note, depending on the analyzed application, the analysis may require a lot of memory and a large stack size. Remember to set the necessary heap size (e.g. -Xmx8g) and stack size (e.g. -Xss60m). ## Report and Error Types -In the standard option, CogniCryptSAST outputs a report to the console. CogniCryptSAST reporst misuses when the code is not compliant with the CrySL rules. For each misuse CogniCryptSAST reports the class and the method the misuse is contained in. There are multiple misuse types: +CogniCryptSAST reports misuses when the code is not compliant with the CrySL rules. For each misuse CogniCryptSAST reports the class and the method the misuse is contained in. There are multiple misuse types: * **ConstraintError**: A constraint of a CrySL rule is violated, e.g., a key is generated with the wrong key size. * **NeverTypeOfError**: Reported when a value was found to be of a certain reference type: For example, a character array containing a password should never be converted from a `String`. (see `KeyStore` rule [here](https://github.com/CROSSINGTUD/Crypto-API-Rules/blob/master/src/de/darmstadt/tu/crossing/KeyStore.cryptsl)). @@ -71,7 +72,14 @@ In the standard option, CogniCryptSAST outputs a report to the consol * **RequiredPredicateError**: An object A expects an object B to have been used correctly (CrySL blocks REQUIRES and ENSURES). For example a `Cipher` object requires a `SecretKey` object to be correctly and securely generated. * **IncompleteOperationError**: The usage of an object may be incomplete: For example a `Cipher`object may be initialized but never used for en- or decryption, this may render the code dead. This error heavily depends on the computed call graph (CHA by default). -When the option `--reportPath ` is chosen, CogniCryptSAST writes the report to the file `CogniCrypt-Report.txt` and additionally outputs the .jimple files of the classes where misuses where found in. Jimple is an intermediate representation close to the syntax of Java. +CogniCryptSAST supports different report formats, which can be set by using `--reportformat` option. The supported formats are: +- `CMD`: The report is printed to the command line. The content is equivalent to the format from the `TXT` option. +- `TXT`: The report is written to the text file `CryptoAnalysis-Report.txt`. The content is equivalent to the format from the `CMD` option. Additionally, the .jimple files of the classes, where misuses were found in, are output. Jimple is an intermediate representation close to the syntax of Java. +- `SARIF`: The report is written to the JSON file `CryptoAnalysis-Report.json`. The content is formatted in the SARIF format. +- `CSV`: The report is written to the CSV file `CryptoAnalysis-Report.csv`. The content is formatted in the CSV format. +- `CSV_SUMMARY`: The report is written to the file `CryptoAnalysis-Report-Summary.csv` and contains a summary of the analysis results. Compared to the `CSV` format, this format does not provide concrete information about the errors, it only lists the amount of each misuse type. This option was previously implemented by the `CSV` option, which has been changed to provide more detailed information about the errors in the CSV format. + +If the `--reportformat` option is not specified, CogniCryptSAST defaults to the `CMD` option. It also allows the usage of multiple different formats for the same analysis (e.g. `--reportformat CMD TXT CSV` creates a report, which is printed to the command line and is written to a text and CSV file). If the option `--reportPath ` is set, the reports are created in the specified directory. ## Updating CrySL Rules diff --git a/pom.xml b/pom.xml index ef729045d..da2e4d3f7 100644 --- a/pom.xml +++ b/pom.xml @@ -7,21 +7,107 @@ de.fraunhofer.iem CryptoAnalysis-Parent - ${revision} + 2.8.0 pom CryptoAnalysis-parent + + + Eclipse Public License - v2.0 + https://www.eclipse.org/legal/epl-2.0/ + + + + + CogniCrypt + CogniCrypt + cognicrypt@eim.upb.de + + + + scm:git:git@github.com:CROSSINGTUD/CryptoAnalysis.git + scm:git:ssh://github.com:CROSSINGTUD/CryptoAnalysis.git + https://github.com/CROSSINGTUD/CryptoAnalysis + + + CogniCrypt_SAST: CrySL-to-Static Analysis Compiler + https://github.com/CROSSINGTUD/CryptoAnalysis + CryptoAnalysis CryptoAnalysis-Android - 2.7.3 UTF-8 3.3.0 + + + + deployment + + + + org.sonatype.plugins + nexus-staging-maven-plugin + 1.6.13 + true + + ossrh + https://s01.oss.sonatype.org + true + + + + org.apache.maven.plugins + maven-gpg-plugin + 3.0.1 + + + sign-artifacts + verify + + sign + + + + --pinentry-mode + loopback + + + + + + + org.apache.maven.plugins + maven-javadoc-plugin + 3.6.0 + + + attach-javadoc + + jar + + + + + + org.apache.maven.plugins + maven-release-plugin + 3.0.1 + + @{project.version} + + + + + + + @@ -36,7 +122,7 @@ org.codehaus.mojo flatten-maven-plugin - 1.2.2 + 1.3.0 true @@ -60,6 +146,7 @@ org.apache.maven.plugins maven-source-plugin + 3.2.1 attach-sources @@ -87,32 +174,14 @@ - - - soot-snapshot - soot snapshot - https://soot-build.cs.uni-paderborn.de/nexus/repository/soot-snapshot/ - - false - - - - soot-release - soot release - https://soot-build.cs.uni-paderborn.de/nexus/repository/soot-release/ - - false - - - - soot-snapshot - https://soot-build.cs.uni-paderborn.de/nexus/repository/soot-snapshot/ + ossrh + https://s01.oss.sonatype.org/content/repositories/snapshots/ - soot-release - https://soot-build.cs.uni-paderborn.de/nexus/repository/soot-release/ + ossrh + https://s01.oss.sonatype.org/service/local/staging/deploy/maven2/