package org.jvnet.hudson.plugins.fortify360; import java.util.*; import java.io.*; import hudson.FilePath; import hudson.remoting.VirtualChannel; import org.apache.commons.io.*; import org.apache.commons.lang.StringEscapeUtils; import org.dom4j.Document; import org.dom4j.DocumentException; import org.dom4j.Node; import org.dom4j.io.SAXReader; public class RemoteService implements FilePath.FileCallable<FPRSummary> { private static final long serialVersionUID = 229830219491170076L; private String fpr; private String filterSet; private String searchCondition; public RemoteService(String fpr, String filterSet, String searchCondition) { this.fpr = fpr; this.filterSet = filterSet; this.searchCondition = searchCondition; } public FPRSummary invoke(File workspace, VirtualChannel channel) throws IOException { File template = null; File outputXml = null; File template2 = null; File outputXml2 = null; FPRSummary summary = new FPRSummary(); try { File realFPR = locateFPR(workspace, fpr); if ( null == realFPR ) { throw new RuntimeException("Can't locate FPR file"); } //String fprFullPath = realFPR.getAbsolutePath(); //summary.setFprFullPath(fprFullPath); summary.setFprFile(new FilePath(realFPR)); if ( SCAMetaInfo.hasReportGenerator() ) { template = saveReportTemplate(); outputXml = createXMLReport(realFPR, template, filterSet); double nvs = calculateNvsFromReport(outputXml); summary.setNvs(nvs); if ( !isEmpty(searchCondition) ) { template2 = saveReportTemplate(true, searchCondition); outputXml2 = createXMLReport(realFPR, template2, filterSet); int count = getCountFromReport(outputXml2); if ( count > 0 ) { summary.setFailedCount(count); } } } else { // no reportGenerator // summary.setNvs(0.0); // summary.setFailedCount(0); } } catch (InterruptedException e) { IOException x = new IOException(); x.initCause(e); throw x; } catch (DocumentException e) { e.printStackTrace(); IOException x = new IOException(); x.initCause(e); throw x; } finally { deleteFile(template); deleteFile(outputXml); deleteFile(template2); deleteFile(outputXml2); } return summary; } private static void deleteFile(File file) { if ( null != file && file.exists() ) { try { //System.out.println("Delete: " + file.getAbsolutePath()); file.delete(); } catch ( Exception e ) { } } } private File saveReportTemplate() throws IOException { return saveReportTemplate(false, null); } private File saveReportTemplate(boolean searchReport, String refinement) throws IOException { InputStream in = null; FileOutputStream out = null; try { String reportTemplate = null; if ( searchReport ) reportTemplate = "org/jvnet/hudson/plugins/fortify360/FPRPublisher/SearchReportDefinition.xml"; else reportTemplate = "org/jvnet/hudson/plugins/fortify360/FPRPublisher/ReportDefinition.xml"; in = this.getClass().getClassLoader().getResourceAsStream(reportTemplate); byte b[] = new byte[8*1024]; int n = in.read(b); if ( searchReport ) { String fileContent = new String(b, 0, n, "UTF-8"); fileContent = fileContent.replace("<Refinement/>", "<Refinement>" + StringEscapeUtils.escapeHtml(refinement) + "</Refinement>"); b = fileContent.getBytes("UTF-8"); n = b.length; } File template = File.createTempFile("template", ".xml"); out = new FileOutputStream(template); out.write(b, 0, n); return template; } finally { IOUtils.closeQuietly(in); IOUtils.closeQuietly(out); } } private static File createXMLReport(File fpr, File template, String filterSet) throws InterruptedException, IOException { String os = System.getProperty("os.name"); // Win: the name is reportGenerator.bat // Linux: the name is ReportGenerator (case sensitive) // I think we have reportGenerator on Mac, but not sure about the name // other platforms: no such utility String image = os.matches("Win.*|.*win.*") ? "reportGenerator.bat" : "ReportGenerator"; File outputXml = File.createTempFile("report", ".xml"); // reportGenerator -format xml -f xx.xml -template c:\issue_by_fpo.xml -source c:\WebGoat5.0\webgoat_57.fpr ArrayList<String> cmd = new ArrayList<String>(); cmd.add(image); cmd.add("-format"); cmd.add("xml"); cmd.add("-f"); cmd.add(outputXml.getAbsolutePath()); cmd.add("-template"); cmd.add(template.getAbsolutePath()); cmd.add("-source"); cmd.add(fpr.getAbsolutePath()); if ( !isEmpty(filterSet) ) { cmd.add("-filterSet"); cmd.add(filterSet); } ProcessBuilder pb = new ProcessBuilder(cmd); System.out.println("EXE: " + cmd.toString()); Process proc = pb.start(); proc.waitFor(); int exitValue = proc.exitValue(); //System.out.println("Exit Value = " + exitValue); if ( 0 != exitValue ) { String err = "While running reportGenerator: " + IOUtils.toString(proc.getErrorStream()); throw new RuntimeException(err); } return outputXml; } /** Calculate NSF of the FPR * <p>For SCA 5.7, the NVS equation is * <br/> NVS = ((((HFPO*10)+(MFPO*1)+(LFPO*.01))*.5)+(((P1*2)+P2*4)+(P3*16)+(PABOVE*64))*.5))/(ExecutableLOC/1000) * </p> * <p>For SCA 5.8 (F360 v2.5), according to Deprecation Note Normalized Vulnerability Score * <br/> NVS = ((((CFPO*10)+(HFPO*5)+(MFPO*1)+(LFPO*0.1))*.5)+(((P1*2)+(P2*4)+(P3*16)+(PABOVE*64))*.5))/(ExecutableLOC/1000) * * @param outputXml * @return * @throws DocumentException */ @SuppressWarnings({ "unchecked", "unchecked" }) private static double calculateNvsFromReport(File outputXml) throws DocumentException { SAXReader reader = new SAXReader(); Document document = reader.read(outputXml); String xPath1 = "//ReportDefinition/ReportSection/SubSection[Title='LOC']/Text"; String loc = document.selectSingleNode(xPath1).getText(); // System.out.println("Loc = " + loc); String xPath2 = "//ReportDefinition/ReportSection/SubSection/IssueListing/Chart/GroupingSection"; List<Node> nodes = document.selectNodes(xPath2); double nvs = 0.0; // NVS = ((((HFPO*10)+(MFPO*1)+(LFPO*.01))*.5)+(((P1*2)+P2*4)+(P3*16)+(PABOVE*64))*.5))/(ExecutableLOC/1000) // High, Medium, Low // Not an Issue, Reliability Issue, Bad Practice, Suspicious, Real Issue|Expliotable for( Node groupSectionNode : nodes ) { String count = groupSectionNode.valueOf("@count"); String title = groupSectionNode.selectSingleNode("groupTitle").getText(); // System.out.println(title + " " + count); boolean newFPO = false; try { newFPO = SCAMetaInfo.isNewFPO(); } catch ( Exception e ) { System.out.println("Error checking SCA version: " + e.getMessage()); } if ( "Critical".equalsIgnoreCase(title) ) { nvs += 0.5*10*Integer.parseInt(count); } if ( "High".equalsIgnoreCase(title) ) { if ( newFPO ) nvs += 0.5*5*Integer.parseInt(count); else nvs += 0.5*10*Integer.parseInt(count); } else if ( "Medium".equalsIgnoreCase(title) ) { // both newFPO and oldFPO are 1 nvs += 0.5*Integer.parseInt(count); } else if ( "Low".equalsIgnoreCase(title) ) { // oldFPO equation is 0.01, I think that's a typo // newFPO is 0.1 nvs += 0.5*0.1*Integer.parseInt(count); } else if ( "Reliability Issue".equalsIgnoreCase(title) ) { nvs += 0.5*2*Integer.parseInt(count); } else if ( "Bad Practice".equalsIgnoreCase(title) ) { nvs += 0.5*4*Integer.parseInt(count); } else if ( "Suspicious".equalsIgnoreCase(title) ) { nvs += 0.5*16*Integer.parseInt(count); } else if ( "Expliotable".equalsIgnoreCase(title) || "Real Issue".equalsIgnoreCase(title) ) { // Sam NG is probably the only one who will change the analysis label, I use "Real Issue" instead of "Exploitable" nvs += 0.5*64*Integer.parseInt(count); } } nvs = nvs/(Integer.parseInt(loc)/1000.0); return nvs; } private static int getCountFromReport(File outputXml) throws DocumentException { SAXReader reader = new SAXReader(); Document document = reader.read(outputXml); String xPath2 = "//ReportDefinition/ReportSection/SubSection/IssueListing/Chart/GroupingSection"; List<Node> nodes = document.selectNodes(xPath2); int total = 0; // NVS = ((((HFPO*10)+(MFPO*1)+(LFPO*.01))*.5)+(((P1*2)+P2*4)+(P3*16)+(PABOVE*64))*.5))/(ExecutableLOC/1000) // High, Medium, Low // Not an Issue, Reliability Issue, Bad Practice, Suspicious, Real Issue|Expliotable for( Node groupSectionNode : nodes ) { String count = groupSectionNode.valueOf("@count"); String title = groupSectionNode.selectSingleNode("groupTitle").getText(); // System.out.println(title + " " + count); // this works for both SCA 5.7 and 5.8 if ( "Critical".equalsIgnoreCase(title) ) { total += Integer.parseInt(count); } else if ( "High".equalsIgnoreCase(title) ) { total += Integer.parseInt(count); } else if ( "Medium".equalsIgnoreCase(title) ) { total += Integer.parseInt(count); } else if ( "Low".equalsIgnoreCase(title) ) { total += Integer.parseInt(count); } } return total; } @SuppressWarnings("unchecked") private static File locateFPR(File path, String preferredFileName) { String ext[] = {"fpr"}; Iterator<File> iterator = FileUtils.iterateFiles(path, ext, true); long latestTime = 0; File latestFile = null; while(iterator.hasNext()) { File file = iterator.next(); if ( isEmpty(preferredFileName) || preferredFileName.equalsIgnoreCase(file.getName()) ) { if ( null == latestFile ) { latestTime = file.lastModified(); latestFile = file; } else { // if this file is newer, we will use this file if ( latestTime < file.lastModified() ) { latestTime = file.lastModified(); latestFile = file; // else if the last modified time is the same, but this file's file name is shorter, we will use this one // this to assume, if you copy the file, the file name is usually "Copy of XXX.fpr" } else if ( latestTime == file.lastModified() && latestFile.getName().length() > file.getName().length() ) { latestFile = file; } } } } return latestFile; } private static boolean isEmpty(String str) { return ( null == str || str.length() == 0 ); } }