////////////////////////////////////////////////////////////////////////
//
// Copyright (c) 2009-2013 Denim Group, Ltd.
//
// The contents of this file are subject to the Mozilla Public License
// Version 2.0 (the "License"); you may not use this file except in
// compliance with the License. You may obtain a copy of the License at
// http://www.mozilla.org/MPL/
//
// Software distributed under the License is distributed on an "AS IS"
// basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the
// License for the specific language governing rights and limitations
// under the License.
//
// The Original Code is ThreadFix.
//
// The Initial Developer of the Original Code is Denim Group, Ltd.
// Portions created by Denim Group, Ltd. are Copyright (C)
// Denim Group, Ltd. All Rights Reserved.
//
// Contributor(s): Denim Group, Ltd.
//
////////////////////////////////////////////////////////////////////////
package com.denimgroup.threadfix.service.remoteprovider;
import java.io.IOException;
import java.io.InputStream;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import javax.net.ssl.HttpsURLConnection;
import org.apache.commons.codec.binary.Base64;
import org.springframework.beans.factory.annotation.Autowired;
import org.xml.sax.Attributes;
import org.xml.sax.SAXException;
import org.xml.sax.helpers.DefaultHandler;
import com.denimgroup.threadfix.data.dao.ChannelSeverityDao;
import com.denimgroup.threadfix.data.dao.ChannelTypeDao;
import com.denimgroup.threadfix.data.dao.ChannelVulnerabilityDao;
import com.denimgroup.threadfix.data.entities.ChannelType;
import com.denimgroup.threadfix.data.entities.DataFlowElement;
import com.denimgroup.threadfix.data.entities.Finding;
import com.denimgroup.threadfix.data.entities.RemoteProviderApplication;
import com.denimgroup.threadfix.data.entities.Scan;
public class VeracodeRemoteProvider extends RemoteProvider {
private static final String GET_APP_BUILDS_URI = "https://analysiscenter.veracode.com/api/2.0/getappbuilds.do";
private static final String GET_DETAILED_REPORT_URI = "https://analysiscenter.veracode.com/api/detailedreport.do";
private String password = null;
private String username = null;
@Autowired
public VeracodeRemoteProvider(ChannelTypeDao channelTypeDao,
ChannelVulnerabilityDao channelVulnerabilityDao,
ChannelSeverityDao channelSeverityDao) {
this.channelVulnerabilityDao = channelVulnerabilityDao;
this.channelTypeDao = channelTypeDao;
this.channelSeverityDao = channelSeverityDao;
setChannelType(ChannelType.VERACODE);
}
@Override
public List<Scan> getScans(RemoteProviderApplication remoteProviderApplication) {
if (remoteProviderApplication == null ||
remoteProviderApplication.getApplicationChannel() == null) {
log.error("Veracode getScan() called with invalid parameters. Returning null");
return null;
}
username = remoteProviderApplication.getRemoteProviderType().getUsername();
password = remoteProviderApplication.getRemoteProviderType().getPassword();
// This block tries to get the latest build for the app and dies if it fails.
InputStream appBuildsInputStream = getUrl(GET_APP_BUILDS_URI,username,password);
String appName = remoteProviderApplication.getNativeId();
VeracodeApplicationIdMapParser parser = new VeracodeApplicationIdMapParser();
List<String> buildIds = null;
if (appBuildsInputStream != null) {
parse(appBuildsInputStream, parser);
buildIds = parser.map.get(appName);
}
if (buildIds == null || buildIds.size() == 0) {
log.warn("No build IDs were parsed.");
return null; // we failed.
} else {
log.warn("Retrieved build IDs " + buildIds + " for application " + appName);
}
List<Scan> scans = new ArrayList<Scan>();
for (String buildId : buildIds) {
if (buildId == null || buildId.trim().equals("")) {
log.warn("Build ID was null or empty. This should never happen.");
continue; // we failed.
} else if (parser.dateMap.get(buildId).before(remoteProviderApplication.getLastImportTime())) {
log.info("Build ID " + buildId + " was scanned before the most recent scan in ThreadFix.");
continue;
}
log.warn("Importing scan for build ID " + buildId + " and application " + appName);
// This block tries to parse the scan corresponding to the build.
inputStream = getUrl(GET_DETAILED_REPORT_URI + "?build_id=" + buildId, username, password);
if (inputStream == null) {
log.warn("Received a bad response from Veracode servers, returning null.");
continue;
}
VeracodeSAXParser scanParser = new VeracodeSAXParser();
Scan resultScan = parseSAXInput(scanParser);
if (resultScan == null) {
log.error("No scan was parsed, something is broken.");
continue;
}
resultScan.setImportTime(parser.dateMap.get(buildId));
resultScan.setApplicationChannel(remoteProviderApplication.getApplicationChannel());
log.info("Veracode scan (Build ID " + buildId + ") was successfully parsed.");
scans.add(resultScan);
}
return scans;
}
@Override
public List<RemoteProviderApplication> fetchApplications() {
if (remoteProviderType == null || remoteProviderType.getUsername() == null ||
remoteProviderType.getPassword() == null) {
log.warn("Insufficient credentials.");
return null;
}
log.info("Fetching Veracode applications.");
password = remoteProviderType.getPassword();
username = remoteProviderType.getUsername();
InputStream stream = null;
stream = getUrl(GET_APP_BUILDS_URI,username,password);
if (stream == null) {
log.warn("Got a bad response from Veracode. Check your username and password.");
return null;
}
VeracodeApplicationBuildsParser parser = new VeracodeApplicationBuildsParser();
parse(stream, parser);
if (parser.list != null && parser.list.size() > 0) {
log.info("Number of Veracode applications found: " + parser.list.size());
} else {
log.warn("No Veracode applications were found. Check your configuration.");
}
return parser.list;
}
public InputStream getUrl(String urlString, String username, String password) {
URL url = null;
try {
url = new URL(urlString);
} catch (MalformedURLException e) {
e.printStackTrace();
return null;
}
HttpsURLConnection m_connect;
try {
m_connect = (HttpsURLConnection) url.openConnection();
setupAuthorization(m_connect, username, password);
InputStream is = m_connect.getInputStream();
return is;
} catch (IOException e) {
e.printStackTrace();
}
return null;
}
public void setupAuthorization(HttpsURLConnection connection,
String username, String password) {
String login = username + ":" + password;
String encodedLogin = new String(Base64.encodeBase64(login.getBytes()));
//String encodedLogin = Base64.encodeBase64String(login.getBytes());
connection.setRequestProperty("Authorization", "Basic " + encodedLogin);
}
public class VeracodeApplicationBuildsParser extends DefaultHandler {
public List<RemoteProviderApplication> list = new ArrayList<RemoteProviderApplication>();
public void startElement (String uri, String name, String qName, Attributes atts) throws SAXException {
if (qName.equals("application")) {
RemoteProviderApplication remoteProviderApplication = new RemoteProviderApplication();
remoteProviderApplication.setNativeId(atts.getValue("app_name"));
remoteProviderApplication.setRemoteProviderType(remoteProviderType);
list.add(remoteProviderApplication);
}
}
}
public class VeracodeApplicationIdMapParser extends DefaultHandler {
public Map<String, List<String>> map = new HashMap<String, List<String>>();
public Map<String, Calendar> dateMap = new HashMap<String, Calendar>();
private String currentAppName = null;
private String currentBuildId = null;
public void startElement (String uri, String name, String qName, Attributes atts) throws SAXException {
if (qName.equals("application")) {
currentAppName = atts.getValue("app_name");
map.put(currentAppName, new ArrayList<String>());
} else if (currentAppName != null && qName.equals("build")) {
currentBuildId = atts.getValue("build_id");
map.get(currentAppName).add(currentBuildId);
} else if (currentAppName != null && currentBuildId != null
&& qName.equals("analysis_unit")) {
String dateString = atts.getValue("published_date");
if (dateString != null && dateString.length() > 5)
dateString = dateString.substring(0,dateString.length() - 5);
Calendar calendar = getCalendarFromString("yyyy-MM-DD'T'HH:mm:ss", dateString);
dateMap.put(currentBuildId, calendar);
}
}
}
public class VeracodeSAXParser extends DefaultHandler {
private boolean inStaticFlaws = true;
private Finding lastFinding = null;
private boolean mitigationProposed = false;
////////////////////////////////////////////////////////////////////
// Event handlers.
////////////////////////////////////////////////////////////////////
public void startElement (String uri, String name, String qName, Attributes atts) {
if ("detailedreport".equals(qName)) {
date = getCalendarFromString("yyyy-MM-dd kk:mm:ss", atts.getValue("last_update_time"));
if (date == null)
date = getCalendarFromString("yyyy-MM-dd kk:mm:ss", atts.getValue("generation_date"));
}
if ("dynamicflaws".equals(qName)) {
inStaticFlaws = false;
}
// TODO look through more Veracode scans and see if the inputvector component is the parameter.
if ("flaw".equals(qName)) {
if ("Fixed".equals(atts.getValue("remediation_status")))
return;
String url = null;
if (atts.getValue("url") != null)
url = atts.getValue("url");
else if (atts.getValue("location") != null)
url = atts.getValue("location");
Finding finding = constructFinding(url,
atts.getValue("vuln_parameter"),
atts.getValue("cweid"),
atts.getValue("severity"));
if (finding != null) {
finding.setNativeId(atts.getValue("issueid"));
// TODO revise this method of deciding whether the finding is static.
finding.setIsStatic(inStaticFlaws);
if (atts.getValue("sourcefile") != null && atts.getValue("sourcefilepath") != null) {
String sourceFileLocation = atts.getValue("sourcefilepath") + atts.getValue("sourcefile");
finding.setSourceFileLocation(sourceFileLocation);
finding.getSurfaceLocation().setPath(sourceFileLocation);
if (atts.getValue("line") != null) {
DataFlowElement dataFlowElement = new DataFlowElement();
dataFlowElement.setFinding(finding);
try {
dataFlowElement.setLineNumber(Integer.valueOf(atts.getValue("line")));
} catch (NumberFormatException e) {
log.error("Non-numeric value '" + atts.getValue("line") + "' found in Veracode results when trying to parse line number.", e);
}
dataFlowElement.setSourceFileName(sourceFileLocation);
finding.setDataFlowElements(new ArrayList<DataFlowElement>());
finding.getDataFlowElements().add(dataFlowElement);
}
}
lastFinding = finding;
mitigationProposed = false;
saxFindingList.add(finding);
}
}
if (mitigationProposed && "mitigation".equals(qName) &&
atts.getValue("action") != null &&
atts.getValue("action").equals("Mitigation Accepted")) {
mitigationProposed = false;
lastFinding.setMarkedFalsePositive(true);
log.info("The false positive mitigation was accepted.");
}
if ("mitigation".equals(qName) && atts.getValue("action") != null
&& atts.getValue("action").equals("Mitigated as Potential False Positive")) {
mitigationProposed = true;
log.info("Found a Finding with false positive mitigation proposed.");
}
}
@Override
public void endElement (String uri, String localName, String qName) throws SAXException {
if (qName.equals("dynamicflaws")) {
if ("dynamicflaws".equals(qName)) {
inStaticFlaws = true;
}
}
}
}
}