mirror of
https://github.com/ysoftdevs/DependencyCheck.git
synced 2026-01-15 08:13:43 +01:00
updates
This commit is contained in:
118
src/main/java/org/codesecure/dependencycheck/App.java
Normal file
118
src/main/java/org/codesecure/dependencycheck/App.java
Normal file
@@ -0,0 +1,118 @@
|
||||
package org.codesecure.dependencycheck;
|
||||
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.IOException;
|
||||
import java.util.List;
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
import java.util.prefs.Preferences;
|
||||
import javax.xml.parsers.ParserConfigurationException;
|
||||
import org.apache.commons.cli.ParseException;
|
||||
import org.codesecure.dependencycheck.data.cpe.CPEQuery;
|
||||
import org.codesecure.dependencycheck.data.cpe.xml.Importer;
|
||||
import org.codesecure.dependencycheck.reporting.ReportGenerator;
|
||||
import org.codesecure.dependencycheck.scanner.Dependency;
|
||||
import org.codesecure.dependencycheck.scanner.Scanner;
|
||||
import org.codesecure.dependencycheck.utils.CliParser;
|
||||
import org.xml.sax.SAXException;
|
||||
|
||||
/*
|
||||
* This file is part of App.
|
||||
*
|
||||
* App is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* App is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with App. If not, see http://www.gnu.org/licenses/.
|
||||
*
|
||||
* Copyright (c) 2012 Jeremy Long. All Rights Reserved.
|
||||
*/
|
||||
|
||||
/**
|
||||
*
|
||||
*
|
||||
* @author Jeremy Long (jeremy.long@gmail.com)
|
||||
*/
|
||||
public class App {
|
||||
|
||||
/**
|
||||
* @param args the command line arguments
|
||||
*/
|
||||
public static void main(String[] args) {
|
||||
//Preferences.systemRoot().put("java.util.logging.config.file", "log.properties");
|
||||
App app = new App();
|
||||
app.run(args);
|
||||
}
|
||||
|
||||
public void run(String[] args) {
|
||||
CliParser cli = new CliParser();
|
||||
try {
|
||||
cli.parse(args);
|
||||
} catch (FileNotFoundException ex) {
|
||||
Logger.getLogger(App.class.getName()).log(Level.SEVERE, null, ex);
|
||||
return;
|
||||
} catch (ParseException ex) {
|
||||
Logger.getLogger(App.class.getName()).log(Level.SEVERE, null, ex);
|
||||
return;
|
||||
}
|
||||
|
||||
if (cli.isGetVersion()) {
|
||||
cli.printVersionInfo();
|
||||
} else if (cli.isLoadCPE()) {
|
||||
loadCPE(cli.getCpeFile());
|
||||
} else if (cli.isRunScan()) {
|
||||
runScan(cli.getReportDirectory(), cli.getApplicationName(), cli.getScanFiles());
|
||||
} else {
|
||||
cli.printHelp();
|
||||
}
|
||||
|
||||
}
|
||||
private void loadCPE(String cpePath) {
|
||||
try {
|
||||
Importer.importXML(cpePath);
|
||||
} catch (ParserConfigurationException ex) {
|
||||
Logger.getLogger(App.class.getName()).log(Level.SEVERE, null, ex);
|
||||
} catch (SAXException ex) {
|
||||
Logger.getLogger(App.class.getName()).log(Level.SEVERE, null, ex);
|
||||
} catch (IOException ex) {
|
||||
Logger.getLogger(App.class.getName()).log(Level.SEVERE, null, ex);
|
||||
}
|
||||
}
|
||||
private void runScan(String reportDirectory, String applicationName, String[] files) {
|
||||
try {
|
||||
Scanner scanner = new Scanner();
|
||||
for (String file : files) {
|
||||
scanner.scan(file);
|
||||
}
|
||||
List<Dependency> dependencies = scanner.getDependencies();
|
||||
CPEQuery query = new CPEQuery();
|
||||
query.open();
|
||||
for (Dependency d : dependencies) {
|
||||
query.determineCPE(d);
|
||||
}
|
||||
query.close();
|
||||
ReportGenerator report = new ReportGenerator();
|
||||
try {
|
||||
report.generateReports(reportDirectory, applicationName, dependencies);
|
||||
} catch (IOException ex) {
|
||||
Logger.getLogger(App.class.getName()).log(Level.SEVERE, null, ex);
|
||||
} catch (Exception ex) {
|
||||
Logger.getLogger(App.class.getName()).log(Level.SEVERE, null, ex);
|
||||
}
|
||||
|
||||
} catch (IOException ex) {
|
||||
Logger.getLogger(App.class.getName()).log(Level.SEVERE, null, ex);
|
||||
} catch (org.apache.lucene.queryParser.ParseException ex) {
|
||||
Logger.getLogger(App.class.getName()).log(Level.SEVERE, null, ex);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,100 @@
|
||||
package org.codesecure.dependencycheck.data;
|
||||
/*
|
||||
* This file is part of DependencyCheck.
|
||||
*
|
||||
* DependencyCheck is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* DependencyCheck is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with DependencyCheck. If not, see http://www.gnu.org/licenses/.
|
||||
*
|
||||
* Copyright (c) 2012 Jeremy Long. All Rights Reserved.
|
||||
*/
|
||||
|
||||
/**
|
||||
* <p>Lucene utils is a set of utilitize written to make constructing
|
||||
* Lucene queries simplier.</p>
|
||||
*
|
||||
* @author Jeremy Long (jeremy.long@gmail.com)
|
||||
*/
|
||||
public final class LuceneUtils {
|
||||
|
||||
/**
|
||||
* Provate contructor as this is a utility class.
|
||||
*/
|
||||
private LuceneUtils() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Appends the text to the supplied StringBuilder escaping Lucene control
|
||||
* characters in the process.
|
||||
*
|
||||
* @param buf a StringBuilder to append the escaped text to
|
||||
* @param text the data to be escaped
|
||||
*/
|
||||
@SuppressWarnings("fallthrough")
|
||||
public static void appendEscapedLuceneQuery(StringBuilder buf,
|
||||
final CharSequence text) {
|
||||
|
||||
if (text == null || buf == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (int i = 0; i < text.length(); i++) {
|
||||
char c = text.charAt(i);
|
||||
switch (c) {
|
||||
case '+':
|
||||
case '-':
|
||||
case '&':
|
||||
case '|':
|
||||
case '!':
|
||||
case '(':
|
||||
case ')':
|
||||
case '{':
|
||||
case '}':
|
||||
case '[':
|
||||
case ']':
|
||||
case '^':
|
||||
case '"':
|
||||
case '~':
|
||||
case '*':
|
||||
case '?':
|
||||
case ':':
|
||||
case '\\':
|
||||
buf.append('\\');
|
||||
default:
|
||||
buf.append(c);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Escapes the text passed in so that it is treated as data instead of
|
||||
* control characters.
|
||||
*
|
||||
* @param text data to be escaped
|
||||
* @return the escaped text.
|
||||
*/
|
||||
public static String escapeLuceneQuery(final CharSequence text) {
|
||||
|
||||
if (text == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
int size = text.length();
|
||||
size = size >> 1;
|
||||
StringBuilder buf = new StringBuilder(size);
|
||||
|
||||
appendEscapedLuceneQuery(buf, text);
|
||||
|
||||
return buf.toString();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
package org.codesecure.dependencycheck.data;
|
||||
/*
|
||||
* This file is part of DependencyCheck.
|
||||
*
|
||||
* DependencyCheck is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* DependencyCheck is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with DependencyCheck. If not, see http://www.gnu.org/licenses/.
|
||||
*
|
||||
* Copyright (c) 2012 Jeremy Long. All Rights Reserved.
|
||||
*/
|
||||
|
||||
/**
|
||||
* VersionAnalyzer is a Lucene Analyzer used to analyze version information.
|
||||
*
|
||||
* @author Jeremy Long (jeremy.long@gmail.com)
|
||||
*/
|
||||
public class VersionAnalyzer {
|
||||
//TODO Implement this...
|
||||
}
|
||||
@@ -0,0 +1,454 @@
|
||||
package org.codesecure.dependencycheck.data.cpe;
|
||||
/*
|
||||
* This file is part of DependencyCheck.
|
||||
*
|
||||
* DependencyCheck is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* DependencyCheck is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with DependencyCheck. If not, see http://www.gnu.org/licenses/.
|
||||
*
|
||||
* Copyright (c) 2012 Jeremy Long. All Rights Reserved.
|
||||
*/
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
import org.apache.lucene.analysis.Analyzer;
|
||||
import org.apache.lucene.document.Document;
|
||||
import org.apache.lucene.index.CorruptIndexException;
|
||||
import org.apache.lucene.index.IndexReader;
|
||||
import org.apache.lucene.queryParser.ParseException;
|
||||
import org.apache.lucene.queryParser.QueryParser;
|
||||
import org.apache.lucene.search.IndexSearcher;
|
||||
import org.apache.lucene.search.Query;
|
||||
import org.apache.lucene.search.ScoreDoc;
|
||||
import org.apache.lucene.search.TopDocs;
|
||||
import org.apache.lucene.store.Directory;
|
||||
import org.apache.lucene.util.Version;
|
||||
import org.codesecure.dependencycheck.data.LuceneUtils;
|
||||
import org.codesecure.dependencycheck.scanner.Dependency;
|
||||
import org.codesecure.dependencycheck.scanner.Evidence.Confidence;
|
||||
|
||||
/**
|
||||
* CPEQuery is a utility class that takes a project dependency and attempts
|
||||
* to decern if there is an associated CPE. It uses the evidence contained
|
||||
* within the dependency to search the Lucene index.
|
||||
*
|
||||
* @author Jeremy Long (jeremy.long@gmail.com)
|
||||
*/
|
||||
public class CPEQuery {
|
||||
|
||||
/**
|
||||
* The maximum number of query results to return.
|
||||
*/
|
||||
static final int MAX_QUERY_RESULTS = 10;
|
||||
/**
|
||||
* The weighting boost to give terms when constructing the Lucene query.
|
||||
*/
|
||||
static final String WEIGHTING_BOOST = "^5";
|
||||
/**
|
||||
* A string representation of a regular expression defining characters
|
||||
* utilized within the CPE Names.
|
||||
*/
|
||||
static final String CLEANSE_CHARACTER_RX = "[^A-Za-z0-9 _-]";
|
||||
/* A string representation of a regular expression used to remove all but
|
||||
* alpha characters.
|
||||
*/
|
||||
static final String CLEANSE_NONALPHA_RX = "[^A-Za-z]*";
|
||||
/**
|
||||
* The additional size to add to a new StringBuilder to account for extra
|
||||
* data that will be written into the string.
|
||||
*/
|
||||
static final int STRING_BUILDER_BUFFER = 20;
|
||||
/**
|
||||
* The Lucene IndexReader.
|
||||
*/
|
||||
private IndexReader indexReader = null;
|
||||
/**
|
||||
* The Lucene IndexSearcher.
|
||||
*/
|
||||
private IndexSearcher indexSearcher = null;
|
||||
/**
|
||||
* The Lucene directory.
|
||||
*/
|
||||
private Directory directory = null;
|
||||
/**
|
||||
* The Lucene Analyzer.
|
||||
*/
|
||||
private Analyzer analyzer = null;
|
||||
/**
|
||||
* The Lucene QueryParser.
|
||||
*/
|
||||
private QueryParser queryParser = null;
|
||||
/**
|
||||
* Indicates whether or not the Lucene Index is open.
|
||||
*/
|
||||
private boolean indexOpen = false;
|
||||
|
||||
/**
|
||||
* Opens the data source.
|
||||
*
|
||||
* @throws IOException when the Lucene directory to be querried does not exist or is corrupt.
|
||||
*/
|
||||
public void open() throws IOException {
|
||||
directory = Index.getDirectory();
|
||||
indexReader = IndexReader.open(directory, true);
|
||||
indexSearcher = new IndexSearcher(indexReader);
|
||||
analyzer = Index.createAnalyzer(); //use the same analyzer as used when indexing
|
||||
//TITLE is the default field because it contains venddor, product, and version all in one.
|
||||
queryParser = new QueryParser(Version.LUCENE_35, Fields.TITLE, analyzer);
|
||||
indexOpen = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Closes the data source.
|
||||
*/
|
||||
public void close() {
|
||||
analyzer.close();
|
||||
analyzer = null;
|
||||
queryParser = null;
|
||||
try {
|
||||
indexSearcher.close();
|
||||
} catch (IOException ex) {
|
||||
Logger.getLogger(CPEQuery.class.getName()).log(Level.SEVERE, null, ex);
|
||||
}
|
||||
indexSearcher = null;
|
||||
try {
|
||||
indexReader.close();
|
||||
} catch (IOException ex) {
|
||||
Logger.getLogger(CPEQuery.class.getName()).log(Level.SEVERE, null, ex);
|
||||
}
|
||||
indexReader = null;
|
||||
try {
|
||||
directory.close();
|
||||
} catch (IOException ex) {
|
||||
Logger.getLogger(CPEQuery.class.getName()).log(Level.SEVERE, null, ex);
|
||||
}
|
||||
directory = null;
|
||||
indexOpen = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the status of the data source - is the index open.
|
||||
* @return true or false.
|
||||
*/
|
||||
public boolean isOpen() {
|
||||
return indexOpen;
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensures that the Lucene index is closed.
|
||||
* @throws Throwable when a throwable is thrown.
|
||||
*/
|
||||
@Override
|
||||
protected void finalize() throws Throwable {
|
||||
super.finalize();
|
||||
if (indexOpen) {
|
||||
close();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Searches the data store of CPE entries, trying to identify the CPE for the given
|
||||
* dependency based on the evidence contained within. The depencency passed in is
|
||||
* updated with any identified CPE values.
|
||||
*
|
||||
* @param dependency the dependency to search for CPE entries on.
|
||||
* @throws CorruptIndexException is thrown when the Lucene index is corrupt.
|
||||
* @throws IOException is thrown when an IOException occurs.
|
||||
* @throws ParseException is thrown when the Lucene query cannot be parsed.
|
||||
*/
|
||||
public void determineCPE(Dependency dependency) throws CorruptIndexException, IOException, ParseException {
|
||||
Confidence vendorConf = Confidence.HIGH;
|
||||
Confidence titleConf = Confidence.HIGH;
|
||||
Confidence versionConf = Confidence.HIGH;
|
||||
|
||||
String vendors = dependency.getVendorEvidence().toString(vendorConf);
|
||||
// if ("".equals(vendors)) {
|
||||
// vendors = STRING_THAT_WILL_NEVER_BE_IN_THE_INDEX;
|
||||
// }
|
||||
String titles = dependency.getTitleEvidence().toString(titleConf);
|
||||
// if ("".equals(titles)) {
|
||||
// titles = STRING_THAT_WILL_NEVER_BE_IN_THE_INDEX;
|
||||
// }
|
||||
String versions = dependency.getVersionEvidence().toString(versionConf);
|
||||
// if ("".equals(versions)) {
|
||||
// versions = STRING_THAT_WILL_NEVER_BE_IN_THE_INDEX;
|
||||
// }
|
||||
|
||||
boolean found = false;
|
||||
int cnt = 0;
|
||||
do {
|
||||
List<Entry> entries = searchCPE(vendors, titles, versions, dependency.getTitleEvidence().getWeighting(),
|
||||
dependency.getVendorEvidence().getWeighting());
|
||||
|
||||
if (entries.size()>0) {
|
||||
List<String> verified = verifyEntries(entries, dependency);
|
||||
if (verified.size() > 0) {
|
||||
found = true;
|
||||
dependency.setCPEs(verified);
|
||||
}
|
||||
}
|
||||
|
||||
if (!found) {
|
||||
int round = cnt % 3;
|
||||
if (round == 0) {
|
||||
vendorConf = reduceConfidence(vendorConf);
|
||||
if (dependency.getVendorEvidence().contains(vendorConf)) {
|
||||
vendors += " " + dependency.getVendorEvidence().toString(vendorConf);
|
||||
} else {
|
||||
cnt += 1;
|
||||
round += 1;
|
||||
}
|
||||
}
|
||||
if (round == 1) {
|
||||
titleConf = reduceConfidence(titleConf);
|
||||
if (dependency.getTitleEvidence().contains(titleConf)) {
|
||||
titles += " " + dependency.getTitleEvidence().toString(titleConf);
|
||||
} else {
|
||||
cnt += 1;
|
||||
round += 1;
|
||||
}
|
||||
}
|
||||
if (round == 2) {
|
||||
versionConf = reduceConfidence(versionConf);
|
||||
if (dependency.getVersionEvidence().contains(versionConf)) {
|
||||
versions += " " + dependency.getVersionEvidence().toString(versionConf);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
} while (!found && (++cnt) < 9);
|
||||
}
|
||||
|
||||
/**
|
||||
* Reduces the given confidence by one level. This returns LOW if the confidence
|
||||
* passed in is not HIGH.
|
||||
*
|
||||
* @param c the confidence to reduce.
|
||||
* @return One less then the confidence passed in.
|
||||
*/
|
||||
private Confidence reduceConfidence(final Confidence c) {
|
||||
if (c == Confidence.HIGH) {
|
||||
return Confidence.MEDIUM;
|
||||
} else {
|
||||
return Confidence.LOW;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Searches the Lucene CPE index to identify possible CPE entries associated
|
||||
* with the supplied vendor, product, and version.
|
||||
*
|
||||
* @param vendor the text used to search the vendor field.
|
||||
* @param product the text used to search the title field.
|
||||
* @param version the text used to search the version field.
|
||||
* @return a list of possible CPE values.
|
||||
* @throws CorruptIndexException when the Lucene index is corrupt.
|
||||
* @throws IOException when the Lucene index is not found.
|
||||
* @throws ParseException when the generated query is not valid.
|
||||
*/
|
||||
protected List<Entry> searchCPE(String vendor, String product, String version)
|
||||
throws CorruptIndexException, IOException, ParseException {
|
||||
return searchCPE(vendor, product, version, null, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Searches the Lucene CPE index to identify possible CPE entries associated with
|
||||
* the supplied vendor, product, and version.</p>
|
||||
*
|
||||
* <p>If either the vendorWeightings or productWeightings lists have been populated
|
||||
* this data is used to add weighting factors to the search.</p>
|
||||
*
|
||||
* @param vendor the text used to search the vendor field.
|
||||
* @param product the text used to search the title field.
|
||||
* @param version the text used to search the version field.
|
||||
* @param vendorWeightings a list of strings to use to add weighting factors to the vendor field.
|
||||
* @param productWeightings Adds a list of strings that will be used to add weighting factors to the title search.
|
||||
* @return a list of possible CPE values.
|
||||
* @throws CorruptIndexException when the Lucene index is corrupt.
|
||||
* @throws IOException when the Lucene index is not found.
|
||||
* @throws ParseException when the generated query is not valid.
|
||||
*/
|
||||
protected List<Entry> searchCPE(String vendor, String product, String version,
|
||||
List<String> vendorWeightings, List<String> productWeightings)
|
||||
throws CorruptIndexException, IOException, ParseException {
|
||||
ArrayList<Entry> ret = new ArrayList<Entry>(MAX_QUERY_RESULTS);
|
||||
|
||||
String searchString = buildSearch(vendor, product, version, vendorWeightings, productWeightings);
|
||||
if (searchString == null) {
|
||||
return ret;
|
||||
}
|
||||
Query query = queryParser.parse(searchString);
|
||||
TopDocs docs = indexSearcher.search(query, MAX_QUERY_RESULTS);
|
||||
for (ScoreDoc d : docs.scoreDocs) {
|
||||
Document doc = indexSearcher.doc(d.doc);
|
||||
Entry entry = Entry.parse(doc);
|
||||
entry.setSearchScore(d.score);
|
||||
if (!ret.contains(entry)) {
|
||||
ret.add(entry);
|
||||
}
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Builds a Lucene search string by properly escaping data and constructing a valid search query.</p>
|
||||
*
|
||||
* <p>If either the possibleVendor or possibleProducts lists have been populated this
|
||||
* data is used to add weighting factors to the search string generated.</p>
|
||||
*
|
||||
* @param vendor text to search the vendor field.
|
||||
* @param product text to search the title field.
|
||||
* @param version text to search the version field.
|
||||
* @param vendorWeighting a list of strings to apply to the vendor
|
||||
* to boost the terms weight.
|
||||
* @param produdctWeightings a list of strings to apply to the product/title
|
||||
* to boost the terms weight.
|
||||
* @return the Lucene query.
|
||||
*/
|
||||
protected String buildSearch(String vendor, String product, String version,
|
||||
List<String> vendorWeighting, List<String> produdctWeightings) {
|
||||
|
||||
StringBuilder sb = new StringBuilder(vendor.length() + product.length()
|
||||
+ version.length() + Fields.PRODUCT.length() + Fields.VERSION.length()
|
||||
+ Fields.VENDOR.length() + STRING_BUILDER_BUFFER);
|
||||
|
||||
if ("".equals(version)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!appendWeightedSearch(sb, Fields.PRODUCT, product.toLowerCase(), produdctWeightings)) {
|
||||
return null;
|
||||
}
|
||||
if (!appendWeightedSearch(sb, Fields.VENDOR, vendor.toLowerCase(), vendorWeighting)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
sb.append(Fields.VERSION).append(":(");
|
||||
if (sb.indexOf("^") > 0) {
|
||||
//if we have a weighting on something else, reduce the weighting on the version a lot
|
||||
for (String v : version.split(" ")) {
|
||||
LuceneUtils.appendEscapedLuceneQuery(sb, v);
|
||||
sb.append("^0.2 ");
|
||||
}
|
||||
} else {
|
||||
LuceneUtils.appendEscapedLuceneQuery(sb, version);
|
||||
}
|
||||
sb.append(")");
|
||||
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* This method constructs a Lucene query for a given field. The searchText
|
||||
* is split into seperate words and if the word is within the list of weighted
|
||||
* words then an additional weighting is applied to the term as it is appended
|
||||
* into the query.
|
||||
*
|
||||
* @param sb a StringBuilder that the query text will be appended to.
|
||||
* @param field the field within the Lucene index that the query is searching.
|
||||
* @param searchText text used to construct the query.
|
||||
* @param weightedText a list of terms that will be considered higher
|
||||
* importance when searching.
|
||||
* @return if the append was successful.
|
||||
*/
|
||||
private boolean appendWeightedSearch(StringBuilder sb, String field, String searchText, List<String> weightedText) {
|
||||
//TODO add a mutator or special analyzer that combines words next to each other and adds them as a key.
|
||||
sb.append(" ").append(field).append(":( ");
|
||||
|
||||
String cleanText = cleanseText(searchText);
|
||||
|
||||
if ("".equals(cleanText)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (weightedText == null || weightedText.isEmpty()) {
|
||||
LuceneUtils.appendEscapedLuceneQuery(sb, cleanText);
|
||||
} else {
|
||||
String[] text = cleanText.split("\\s");
|
||||
for (String word : text) {
|
||||
String temp = null;
|
||||
for (String weighted : weightedText) {
|
||||
String weightedStr = cleanseText(weighted);
|
||||
if (equalsIgnoreCaseAndNonAlpha(word, weightedStr)) {
|
||||
temp = LuceneUtils.escapeLuceneQuery(word) + WEIGHTING_BOOST;
|
||||
if (!word.equalsIgnoreCase(weightedStr)) {
|
||||
temp += " " + LuceneUtils.escapeLuceneQuery(weightedStr) + WEIGHTING_BOOST;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (temp == null) {
|
||||
temp = LuceneUtils.escapeLuceneQuery(word);
|
||||
}
|
||||
sb.append(" ").append(temp);
|
||||
}
|
||||
}
|
||||
sb.append(" ) ");
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes characters from the input text that are not used within the CPE index.
|
||||
*
|
||||
* @param text is the text to remove the characters from.
|
||||
* @return the text having removed some characters.
|
||||
*/
|
||||
private String cleanseText(String text) {
|
||||
return text.replaceAll(CLEANSE_CHARACTER_RX, " ");
|
||||
}
|
||||
|
||||
/**
|
||||
* Compares two strings after lower casing them and removing the non-alpha
|
||||
* characters.
|
||||
*
|
||||
* @param l string one to compare.
|
||||
* @param r string two to compare.
|
||||
* @return whether or not the two strings are similiar.
|
||||
*/
|
||||
private boolean equalsIgnoreCaseAndNonAlpha(String l, String r) {
|
||||
if (l == null || r == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
String left = l.replaceAll(CLEANSE_NONALPHA_RX, "");
|
||||
String right = r.replaceAll(CLEANSE_NONALPHA_RX, "");
|
||||
return left.equalsIgnoreCase(right);
|
||||
}
|
||||
|
||||
/**
|
||||
* Takes a list of entries and a dependency. If the entry has terms that were
|
||||
* used (i.e. this CPE entry wasn't identified because the version matched
|
||||
* but the product and title did not) then the CPE Entry is returned in a list
|
||||
* of possible CPE Entries.
|
||||
*
|
||||
* @param entries a list of CPE entries.
|
||||
* @param dependency the dependency that the CPE entries could be for.
|
||||
* @return a list of matched CPE entries.
|
||||
*/
|
||||
private List<String> verifyEntries(final List<Entry> entries, final Dependency dependency) {
|
||||
List<String> verified = new ArrayList<String>();
|
||||
for (Entry e : entries) {
|
||||
if (dependency.getTitleEvidence().containsUsedString(e.getProduct())
|
||||
&& dependency.getVendorEvidence().containsUsedString(e.getVendor())) {
|
||||
//TODO - determine if this is right? Should we be carrying too much about the
|
||||
// version at this point? Likely need to implement the versionAnalyzer....
|
||||
if (dependency.getVersionEvidence().containsUsedString(e.getVersion())) {
|
||||
verified.add(e.getName());
|
||||
}
|
||||
}
|
||||
}
|
||||
return verified;
|
||||
}
|
||||
}
|
||||
380
src/main/java/org/codesecure/dependencycheck/data/cpe/Entry.java
Normal file
380
src/main/java/org/codesecure/dependencycheck/data/cpe/Entry.java
Normal file
@@ -0,0 +1,380 @@
|
||||
package org.codesecure.dependencycheck.data.cpe;
|
||||
/*
|
||||
* This file is part of DependencyCheck.
|
||||
*
|
||||
* DependencyCheck is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* DependencyCheck is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with DependencyCheck. If not, see http://www.gnu.org/licenses/.
|
||||
*
|
||||
* Copyright (c) 2012 Jeremy Long. All Rights Reserved.
|
||||
*/
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.net.URLDecoder;
|
||||
import java.text.ParseException;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.Date;
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
import org.apache.lucene.document.Document;
|
||||
|
||||
/**
|
||||
* A single CPE entry from the cpe.xml downloaded from
|
||||
* <a href="http://nvd.nist.gov/cpe.cfm">http://nvd.nist.gov/cpe.cfm</a>.
|
||||
*
|
||||
* @author Jeremy Long (jeremy.long@gmail.com)
|
||||
*/
|
||||
public class Entry {
|
||||
|
||||
/**
|
||||
* This parse method does not fully convert a Lucene Document into a CPE Entry;
|
||||
* it only sets the Entry.Name.
|
||||
*
|
||||
* @param doc a Lucene Document.
|
||||
* @return a CPE Entry.
|
||||
*/
|
||||
public static Entry parse(Document doc) {
|
||||
Entry entry = new Entry();
|
||||
try {
|
||||
entry.setName(doc.get(Fields.NAME));
|
||||
entry.setTitle(doc.get(Fields.TITLE));
|
||||
} catch (UnsupportedEncodingException ex) {
|
||||
Logger.getLogger(Entry.class.getName()).log(Level.SEVERE, null, ex);
|
||||
entry.name = doc.get(Fields.NAME);
|
||||
}
|
||||
// entry.vendor = doc.get(Fields.VENDOR);
|
||||
// entry.version = doc.get(Fields.VERSION);
|
||||
// //entry.revision = doc.get(Fields.REVISION);
|
||||
// entry.product = doc.get(Fields.TITLE);
|
||||
// entry.nvdId = doc.get(Fields.NVDID);
|
||||
return entry;
|
||||
}
|
||||
/**
|
||||
* The title of the CPE
|
||||
*/
|
||||
protected String title;
|
||||
|
||||
/**
|
||||
* Get the value of title
|
||||
*
|
||||
* @return the value of title
|
||||
*/
|
||||
public String getTitle() {
|
||||
return title;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the value of title
|
||||
*
|
||||
* @param title new value of title
|
||||
*/
|
||||
public void setTitle(String title) {
|
||||
this.title = title;
|
||||
}
|
||||
/**
|
||||
* The name of the CPE entry.
|
||||
*/
|
||||
protected String name;
|
||||
|
||||
/**
|
||||
* Get the value of name
|
||||
*
|
||||
* @return the value of name
|
||||
*/
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the value of name and calls parseName to obtain the vendor:product:version:revision
|
||||
*
|
||||
* @param name new value of name
|
||||
* @throws UnsupportedEncodingException should never be thrown...
|
||||
*/
|
||||
public void setName(String name) throws UnsupportedEncodingException {
|
||||
this.name = name;
|
||||
parseName();
|
||||
}
|
||||
/**
|
||||
* The status of the CPE Entry.
|
||||
*/
|
||||
protected String status;
|
||||
|
||||
/**
|
||||
* Get the value of status
|
||||
*
|
||||
* @return the value of status
|
||||
*/
|
||||
public String getStatus() {
|
||||
return status;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the value of status
|
||||
*
|
||||
* @param status new value of status
|
||||
*/
|
||||
public void setStatus(String status) {
|
||||
this.status = status;
|
||||
}
|
||||
/**
|
||||
* The modification date of the CPE Entry.
|
||||
*/
|
||||
protected Date modificationDate;
|
||||
|
||||
/**
|
||||
* Get the value of modificationDate
|
||||
*
|
||||
* @return the value of modificationDate
|
||||
*/
|
||||
public Date getModificationDate() {
|
||||
return modificationDate;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the value of modificationDate
|
||||
*
|
||||
* @param modificationDate new value of modificationDate
|
||||
*/
|
||||
public void setModificationDate(Date modificationDate) {
|
||||
this.modificationDate = modificationDate;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the value of modificationDate
|
||||
*
|
||||
* Expected format: yyyy-MM-dd'T'HH:mm:ss.SSS'Z'
|
||||
*
|
||||
* @param modificationDate new value of modificationDate
|
||||
* @throws ParseException is thrown when a parse exception occurs.
|
||||
*/
|
||||
public void setModificationDate(String modificationDate) throws ParseException {
|
||||
|
||||
String formatStr = "yyyy-MM-dd'T'HH:mm:ss.SSS'Z'";
|
||||
Date tempDate = null;
|
||||
SimpleDateFormat sdf = new SimpleDateFormat(formatStr);
|
||||
sdf.setLenient(true);
|
||||
tempDate = sdf.parse(modificationDate);
|
||||
|
||||
this.modificationDate = tempDate;
|
||||
}
|
||||
/**
|
||||
* The nvdId.
|
||||
*/
|
||||
protected String nvdId;
|
||||
|
||||
/**
|
||||
* Get the value of nvdId
|
||||
*
|
||||
* @return the value of nvdId
|
||||
*/
|
||||
public String getNvdId() {
|
||||
return nvdId;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the value of nvdId
|
||||
*
|
||||
* @param nvdId new value of nvdId
|
||||
*/
|
||||
public void setNvdId(String nvdId) {
|
||||
this.nvdId = nvdId;
|
||||
}
|
||||
/**
|
||||
* The vendor name.
|
||||
*/
|
||||
protected String vendor;
|
||||
|
||||
/**
|
||||
* Get the value of vendor
|
||||
*
|
||||
* @return the value of vendor
|
||||
*/
|
||||
public String getVendor() {
|
||||
return vendor;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the value of vendor
|
||||
*
|
||||
* @param vendor new value of vendor
|
||||
*/
|
||||
public void setVendor(String vendor) {
|
||||
this.vendor = vendor;
|
||||
}
|
||||
/**
|
||||
* The product name.
|
||||
*/
|
||||
protected String product;
|
||||
|
||||
/**
|
||||
* Get the value of product
|
||||
*
|
||||
* @return the value of product
|
||||
*/
|
||||
public String getProduct() {
|
||||
return product;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the value of product
|
||||
*
|
||||
* @param product new value of product
|
||||
*/
|
||||
public void setProduct(String product) {
|
||||
this.product = product;
|
||||
}
|
||||
/**
|
||||
* The product version.
|
||||
*/
|
||||
protected String version;
|
||||
|
||||
/**
|
||||
* Get the value of version
|
||||
*
|
||||
* @return the value of version
|
||||
*/
|
||||
public String getVersion() {
|
||||
return version;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the value of version
|
||||
*
|
||||
* @param version new value of version
|
||||
*/
|
||||
public void setVersion(String version) {
|
||||
this.version = version;
|
||||
}
|
||||
/**
|
||||
* The product revision.
|
||||
*/
|
||||
protected String revision;
|
||||
|
||||
/**
|
||||
* Get the value of revision
|
||||
*
|
||||
* @return the value of revision
|
||||
*/
|
||||
public String getRevision() {
|
||||
return revision;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the value of revision
|
||||
*
|
||||
* @param revision new value of revision
|
||||
*/
|
||||
public void setRevision(String revision) {
|
||||
this.revision = revision;
|
||||
}
|
||||
/**
|
||||
* If the CPE Entry is well known (i.e. based off a hash)
|
||||
*/
|
||||
protected boolean wellKnown = false;
|
||||
|
||||
/**
|
||||
* Get the value of wellKnown
|
||||
*
|
||||
* @return the value of wellKnown
|
||||
*/
|
||||
public boolean isWellKnown() {
|
||||
return wellKnown;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the value of wellKnown
|
||||
*
|
||||
* @param wellKnown new value of wellKnown
|
||||
*/
|
||||
public void setWellKnown(boolean wellKnown) {
|
||||
this.wellKnown = wellKnown;
|
||||
}
|
||||
/**
|
||||
* The search score.
|
||||
*/
|
||||
protected float searchScore;
|
||||
|
||||
/**
|
||||
* Get the value of searchScore
|
||||
*
|
||||
* @return the value of searchScore
|
||||
*/
|
||||
public float getSearchScore() {
|
||||
return searchScore;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the value of searchScore
|
||||
*
|
||||
* @param searchScore new value of searchScore
|
||||
*/
|
||||
public void setSearchScore(float searchScore) {
|
||||
this.searchScore = searchScore;
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Parses a name attribute value, from the cpe.xml, into its
|
||||
* corresponding parts: vendor, product, version, revision.</p>
|
||||
* <p>Example:</p>
|
||||
* <code> cpe:/a:apache:struts:1.1:rc2</code>
|
||||
*
|
||||
* <p>Results in:</p>
|
||||
* <ul>
|
||||
* <li>Vendor: apache</li>
|
||||
* <li>Product: struts</li>
|
||||
* <li>Version: 1.1</li>
|
||||
* <li>Revision: rc2</li>
|
||||
* </ul>
|
||||
*
|
||||
* @throws UnsupportedEncodingException should never be thrown...
|
||||
*/
|
||||
private void parseName() throws UnsupportedEncodingException {
|
||||
if (name != null && name.length() > 7) {
|
||||
String[] data = name.substring(7).split(":");
|
||||
if (data.length >= 1) {
|
||||
vendor = URLDecoder.decode(data[0], "UTF-8");
|
||||
if (data.length >= 2) {
|
||||
product = URLDecoder.decode(data[1], "UTF-8");
|
||||
if (data.length >= 3) {
|
||||
version = URLDecoder.decode(data[2], "UTF-8");
|
||||
if (data.length >= 4) {
|
||||
revision = URLDecoder.decode(data[3], "UTF-8");
|
||||
}
|
||||
//ignore edition and language fields.. don't really see them used in the a:
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (obj == null) {
|
||||
return false;
|
||||
}
|
||||
if (getClass() != obj.getClass()) {
|
||||
return false;
|
||||
}
|
||||
final Entry other = (Entry) obj;
|
||||
if ((this.name == null) ? (other.name != null) : !this.name.equals(other.name)) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
int hash = 5;
|
||||
hash = 83 * hash + (this.name != null ? this.name.hashCode() : 0);
|
||||
return hash;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
package org.codesecure.dependencycheck.data.cpe;
|
||||
/*
|
||||
* This file is part of DependencyCheck.
|
||||
*
|
||||
* DependencyCheck is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* DependencyCheck is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with DependencyCheck. If not, see http://www.gnu.org/licenses/.
|
||||
*
|
||||
* Copyright (c) 2012 Jeremy Long. All Rights Reserved.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Fields is a collection of field names used within the Lucene index for CPE
|
||||
* entries.
|
||||
*
|
||||
* @author Jeremy Long (jeremy.long@gmail.com)
|
||||
*/
|
||||
public abstract class Fields {
|
||||
/**
|
||||
* The key for the name field.
|
||||
*/
|
||||
public static final String NAME = "name";
|
||||
/**
|
||||
* The key for the vendor field.
|
||||
*/
|
||||
public static final String VENDOR = "vendor";
|
||||
/**
|
||||
* The key for the version field.
|
||||
*/
|
||||
public static final String VERSION = "version";
|
||||
//public static final String REVISION = "revision";
|
||||
/**
|
||||
* The key for the product field.
|
||||
*/
|
||||
public static final String PRODUCT = "product";
|
||||
/**
|
||||
* The key for the title field. This is a field combining vendor, product, and version.
|
||||
*/
|
||||
public static final String TITLE = "title";
|
||||
/**
|
||||
* The key for the nvdId field.
|
||||
*/
|
||||
public static final String NVDID = "nvdid";
|
||||
}
|
||||
143
src/main/java/org/codesecure/dependencycheck/data/cpe/Index.java
Normal file
143
src/main/java/org/codesecure/dependencycheck/data/cpe/Index.java
Normal file
@@ -0,0 +1,143 @@
|
||||
package org.codesecure.dependencycheck.data.cpe;
|
||||
/*
|
||||
* This file is part of DependencyCheck.
|
||||
*
|
||||
* DependencyCheck is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* DependencyCheck is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with DependencyCheck. If not, see http://www.gnu.org/licenses/.
|
||||
*
|
||||
* Copyright (c) 2012 Jeremy Long. All Rights Reserved.
|
||||
*/
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.net.URL;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
import org.apache.lucene.analysis.Analyzer;
|
||||
import org.apache.lucene.analysis.KeywordAnalyzer;
|
||||
import org.apache.lucene.analysis.PerFieldAnalyzerWrapper;
|
||||
import org.apache.lucene.analysis.standard.StandardAnalyzer;
|
||||
import org.apache.lucene.index.CorruptIndexException;
|
||||
import org.apache.lucene.index.IndexWriter;
|
||||
import org.apache.lucene.index.IndexWriterConfig;
|
||||
import org.apache.lucene.store.Directory;
|
||||
import org.apache.lucene.store.FSDirectory;
|
||||
import org.apache.lucene.util.Version;
|
||||
import org.codesecure.dependencycheck.utils.Downloader;
|
||||
import org.codesecure.dependencycheck.utils.Settings;
|
||||
import org.codesecure.dependencycheck.data.cpe.xml.Importer;
|
||||
|
||||
/**
|
||||
* The Index class is used to utilize and maintain the CPE Index.
|
||||
*
|
||||
* @author Jeremy Long (jeremy.long@gmail.com)
|
||||
*/
|
||||
public class Index {
|
||||
|
||||
/**
|
||||
* The Lucene directory containing the index.
|
||||
*/
|
||||
protected Directory directory = null;
|
||||
/**
|
||||
* The IndexWriter for the Lucene index.
|
||||
*/
|
||||
protected IndexWriter indexWriter = null;
|
||||
|
||||
/**
|
||||
* Opens the CPE Index.
|
||||
* @throws IOException is thrown if an IOException occurs opening the index.
|
||||
*/
|
||||
public void open() throws IOException {
|
||||
directory = Index.getDirectory();
|
||||
|
||||
Analyzer analyzer = Index.createAnalyzer(); //new StandardAnalyzer(Version.LUCENE_35);
|
||||
IndexWriterConfig conf = new IndexWriterConfig(Version.LUCENE_35, analyzer);
|
||||
indexWriter = new IndexWriter(directory, conf);
|
||||
}
|
||||
|
||||
/**
|
||||
* Closes the CPE Index.
|
||||
*
|
||||
* @throws CorruptIndexException is thrown if the index is corrupt.
|
||||
* @throws IOException is thrown if an IOException occurs closing the index.
|
||||
*/
|
||||
public void close() throws CorruptIndexException, IOException {
|
||||
indexWriter.commit();
|
||||
indexWriter.close(true);
|
||||
directory.close();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the directory that holds the CPE Index.
|
||||
*
|
||||
* @return the Directory containing the CPE Index.
|
||||
* @throws IOException is thrown if an IOException occurs.
|
||||
*/
|
||||
public static Directory getDirectory() throws IOException {
|
||||
String fileName = Settings.getString(Settings.KEYS.CPE_INDEX);
|
||||
File path = new File(fileName);
|
||||
Directory directory = FSDirectory.open(path);
|
||||
|
||||
return directory;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an Analyzer for the CPE Index.
|
||||
*
|
||||
* @return the CPE Analyzer.
|
||||
*/
|
||||
public static Analyzer createAnalyzer() {
|
||||
Map fieldAnalyzers = new HashMap();
|
||||
|
||||
fieldAnalyzers.put(Fields.VERSION, new KeywordAnalyzer());
|
||||
//new WhitespaceAnalyzer(Version.LUCENE_35)); //
|
||||
|
||||
PerFieldAnalyzerWrapper wrapper = new PerFieldAnalyzerWrapper(
|
||||
new StandardAnalyzer(Version.LUCENE_35), fieldAnalyzers);
|
||||
|
||||
return wrapper;
|
||||
}
|
||||
|
||||
/**
|
||||
* Downloads the latest CPE XML file from the web and imports it into
|
||||
* the current CPE Index.
|
||||
*
|
||||
* @throws Exception is thrown if an exception occurs.
|
||||
*/
|
||||
public void updateIndexFromWeb() throws Exception {
|
||||
if (updateNeeded()) {
|
||||
URL url = new URL(Settings.getString(Settings.KEYS.CPE_URL));
|
||||
File outputPath = null;
|
||||
try {
|
||||
outputPath = File.createTempFile("cpe", ".xml");
|
||||
Downloader.fetchFile(url, outputPath);
|
||||
Importer.importXML(outputPath.toString());
|
||||
} catch (Exception ex) {
|
||||
Logger.getLogger(Index.class.getName()).log(Level.SEVERE, null, ex);
|
||||
} finally {
|
||||
outputPath.delete();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines if the index needs to be updated.
|
||||
*
|
||||
* @return whether or not the CPE Index needs to be updated.
|
||||
*/
|
||||
public boolean updateNeeded() {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,100 @@
|
||||
package org.codesecure.dependencycheck.data.cpe;
|
||||
/*
|
||||
* This file is part of DependencyCheck.
|
||||
*
|
||||
* DependencyCheck is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* DependencyCheck is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with DependencyCheck. If not, see http://www.gnu.org/licenses/.
|
||||
*
|
||||
* Copyright (c) 2012 Jeremy Long. All Rights Reserved.
|
||||
*/
|
||||
|
||||
import java.io.IOException;
|
||||
import org.apache.lucene.document.Document;
|
||||
import org.apache.lucene.document.Field;
|
||||
import org.apache.lucene.index.CorruptIndexException;
|
||||
import org.apache.lucene.index.FieldInfo.IndexOptions;
|
||||
import org.apache.lucene.index.Term;
|
||||
import org.codesecure.dependencycheck.data.LuceneUtils;
|
||||
import org.codesecure.dependencycheck.data.cpe.xml.EntrySaveDelegate;
|
||||
|
||||
/**
|
||||
* The Indexer is used to convert a CPE Entry, retrieved from the CPE XML file,
|
||||
* into a Document that is stored in the Lucene index.
|
||||
*
|
||||
* @author Jeremy Long (jeremy.long@gmail.com)
|
||||
*/
|
||||
public class Indexer extends Index implements EntrySaveDelegate {
|
||||
|
||||
/**
|
||||
* Saves a CPE Entry into the Lucene index.
|
||||
*
|
||||
* @param entry a CPE entry.
|
||||
* @throws CorruptIndexException is thrown if the index is corrupt.
|
||||
* @throws IOException is thrown if an IOException occurs.
|
||||
*/
|
||||
public void saveEntry(Entry entry) throws CorruptIndexException, IOException {
|
||||
Document doc = convertEntryToDoc(entry);
|
||||
Term term = new Term(Fields.NVDID, LuceneUtils.escapeLuceneQuery(entry.getNvdId()));
|
||||
indexWriter.updateDocument(term, doc);
|
||||
}
|
||||
|
||||
/**
|
||||
* Converst a CPE entry into a Lucene Document.
|
||||
*
|
||||
* @param entry a CPE Entry.
|
||||
* @return a Lucene Document containing a CPE Entry.
|
||||
*/
|
||||
protected Document convertEntryToDoc(Entry entry) {
|
||||
Document doc = new Document();
|
||||
|
||||
Field name = new Field(Fields.NAME, entry.getName(), Field.Store.YES, Field.Index.ANALYZED);
|
||||
name.setIndexOptions(IndexOptions.DOCS_ONLY);
|
||||
doc.add(name);
|
||||
|
||||
Field nvdId = new Field(Fields.NVDID, entry.getNvdId(), Field.Store.NO, Field.Index.ANALYZED);
|
||||
nvdId.setIndexOptions(IndexOptions.DOCS_ONLY);
|
||||
doc.add(nvdId);
|
||||
|
||||
Field vendor = new Field(Fields.VENDOR, entry.getVendor(), Field.Store.NO, Field.Index.ANALYZED);
|
||||
vendor.setIndexOptions(IndexOptions.DOCS_ONLY);
|
||||
vendor.setBoost(5.0F);
|
||||
doc.add(vendor);
|
||||
|
||||
Field product = new Field(Fields.PRODUCT, entry.getProduct(), Field.Store.NO, Field.Index.ANALYZED);
|
||||
product.setIndexOptions(IndexOptions.DOCS_ONLY);
|
||||
product.setBoost(5.0F);
|
||||
doc.add(product);
|
||||
|
||||
Field title = new Field(Fields.TITLE, entry.getTitle(), Field.Store.NO, Field.Index.ANALYZED);
|
||||
title.setIndexOptions(IndexOptions.DOCS_ONLY);
|
||||
//title.setBoost(1.0F);
|
||||
doc.add(title);
|
||||
|
||||
//TODO revision should likely be its own field
|
||||
if (entry.getVersion() != null) {
|
||||
Field version = null;
|
||||
if (entry.getRevision() != null) {
|
||||
version = new Field(Fields.VERSION, entry.getVersion() + " "
|
||||
+ entry.getRevision(), Field.Store.NO, Field.Index.ANALYZED);
|
||||
} else {
|
||||
version = new Field(Fields.VERSION, entry.getVersion(),
|
||||
Field.Store.NO, Field.Index.ANALYZED);
|
||||
}
|
||||
version.setIndexOptions(IndexOptions.DOCS_ONLY);
|
||||
version.setBoost(0.8F);
|
||||
doc.add(version);
|
||||
}
|
||||
|
||||
return doc;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
/**
|
||||
* <html>
|
||||
* <head>
|
||||
* <title>org.codesecure.dependencycheck.data.cpe</title>
|
||||
* </head>
|
||||
* <body>
|
||||
* Contains classes for working with the CPE Lucene Index.
|
||||
* </body>
|
||||
* </html>
|
||||
*/
|
||||
|
||||
package org.codesecure.dependencycheck.data.cpe;
|
||||
@@ -0,0 +1,350 @@
|
||||
package org.codesecure.dependencycheck.data.cpe.xml;
|
||||
/*
|
||||
* This file is part of DependencyCheck.
|
||||
*
|
||||
* DependencyCheck is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* DependencyCheck is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with DependencyCheck. If not, see http://www.gnu.org/licenses/.
|
||||
*
|
||||
* Copyright (c) 2012 Jeremy Long. All Rights Reserved.
|
||||
*/
|
||||
|
||||
import org.codesecure.dependencycheck.data.cpe.Entry;
|
||||
import java.io.IOException;
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.text.ParseException;
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
import org.apache.lucene.index.CorruptIndexException;
|
||||
import org.xml.sax.Attributes;
|
||||
import org.xml.sax.SAXException;
|
||||
import org.xml.sax.helpers.DefaultHandler;
|
||||
|
||||
/**
|
||||
* A SAX Handler that will parse the CPE XML Listing.
|
||||
*
|
||||
* @author Jeremy Long (jeremy.long@gmail.com)
|
||||
*/
|
||||
public class CPEHandler extends DefaultHandler {
|
||||
|
||||
private static final String CURRENT_SCHEMA_VERSION = "2.2";
|
||||
EntrySaveDelegate saveDelegate = null;
|
||||
Entry entry = null;
|
||||
boolean languageIsUS = false;
|
||||
StringBuilder nodeText = null;
|
||||
boolean skip = false;
|
||||
Element current = new Element();
|
||||
|
||||
/**
|
||||
* Register a EntrySaveDelegate object. When the last node of an entry is
|
||||
* reached if a save delegate has been regsitered the save method will be invoked.
|
||||
*
|
||||
* @param delegate the delegate used to save an entry
|
||||
*/
|
||||
public void registerSaveDelegate(EntrySaveDelegate delegate) {
|
||||
this.saveDelegate = delegate;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException {
|
||||
nodeText = null;
|
||||
current.setNode(qName);
|
||||
if (current.isCpeItemNode()) {
|
||||
entry = new Entry();
|
||||
String temp = attributes.getValue("deprecated");
|
||||
String name = attributes.getValue("name");
|
||||
skip = (temp != null && temp.equals("true"));
|
||||
try {
|
||||
if (!skip && name.startsWith("cpe:/a:")) {
|
||||
entry.setName(name);
|
||||
} else {
|
||||
skip = true;
|
||||
}
|
||||
} catch (UnsupportedEncodingException ex) {
|
||||
throw new SAXException(ex);
|
||||
}
|
||||
} else if (current.isTitleNode()) {
|
||||
nodeText = new StringBuilder(100);
|
||||
if ("en-US".equalsIgnoreCase(attributes.getValue("xml:lang"))) {
|
||||
languageIsUS = true;
|
||||
} else {
|
||||
languageIsUS = false;
|
||||
}
|
||||
} else if (current.isMetaNode()) {
|
||||
try {
|
||||
entry.setModificationDate(attributes.getValue("modification-date"));
|
||||
} catch (ParseException ex) {
|
||||
Logger.getLogger(CPEHandler.class.getName()).log(Level.SEVERE, null, ex);
|
||||
}
|
||||
entry.setStatus(attributes.getValue("status"));
|
||||
entry.setNvdId(attributes.getValue("nvd-id"));
|
||||
} else if (current.isSchemaVersionNode()) {
|
||||
nodeText = new StringBuilder(3);
|
||||
} else if (current.isTimestampNode()) {
|
||||
nodeText = new StringBuilder(24);
|
||||
}
|
||||
// } else if (current.isCpeListNode()) {
|
||||
// //do nothing
|
||||
// } else if (current.isNotesNode()) {
|
||||
// //do nothing
|
||||
// } else if (current.isNoteNode()) {
|
||||
// //do nothing
|
||||
// } else if (current.isCheckNode()) {
|
||||
// //do nothing
|
||||
// } else if (current.isGeneratorNode()) {
|
||||
// //do nothing
|
||||
// } else if (current.isProductNameNode()) {
|
||||
// //do nothing
|
||||
// } else if (current.isProductVersionNode()) {
|
||||
// //do nothing
|
||||
}
|
||||
|
||||
@Override
|
||||
public void characters(char[] ch, int start, int length) throws SAXException {
|
||||
//nodeText += new String(ch, start, length);
|
||||
if (nodeText != null) {
|
||||
nodeText.append(ch, start, length);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void endElement(String uri, String localName, String qName) throws SAXException {
|
||||
current.setNode(qName);
|
||||
if (current.isCpeItemNode()) {
|
||||
if (saveDelegate != null && !skip) {
|
||||
try {
|
||||
saveDelegate.saveEntry(entry);
|
||||
} catch (CorruptIndexException ex) {
|
||||
Logger.getLogger(CPEHandler.class.getName()).log(Level.SEVERE, null, ex);
|
||||
throw new SAXException(ex);
|
||||
} catch (IOException ex) {
|
||||
Logger.getLogger(CPEHandler.class.getName()).log(Level.SEVERE, null, ex);
|
||||
throw new SAXException(ex);
|
||||
}
|
||||
entry = null;
|
||||
}
|
||||
} else if (current.isTitleNode()) {
|
||||
if (languageIsUS) {
|
||||
entry.setTitle(nodeText.toString());
|
||||
}
|
||||
} else if (current.isSchemaVersionNode() && !CURRENT_SCHEMA_VERSION.equals(nodeText.toString())) {
|
||||
throw new SAXException("ERROR: Invalid Schema Version, expected: "
|
||||
+ CURRENT_SCHEMA_VERSION + ", file is: " + nodeText);
|
||||
}
|
||||
// } else if (current.isCpeListNode()) {
|
||||
// //do nothing
|
||||
// } else if (current.isMetaNode()) {
|
||||
// //do nothing
|
||||
// } else if (current.isNotesNode()) {
|
||||
// //do nothing
|
||||
// } else if (current.isNoteNode()) {
|
||||
// //do nothing
|
||||
// } else if (current.isCheckNode()) {
|
||||
// //do nothing
|
||||
// } else if (current.isGeneratorNode()) {
|
||||
// //do nothing
|
||||
// } else if (current.isProductNameNode()) {
|
||||
// //do nothing
|
||||
// } else if (current.isProductVersionNode()) {
|
||||
// //do nothing
|
||||
// else if (current.isTimestampNode()) {
|
||||
// //do nothing
|
||||
// } else {
|
||||
// throw new SAXException("ERROR STATE: Unexpected qName '" + qName + "'");
|
||||
// }
|
||||
}
|
||||
|
||||
// <editor-fold defaultstate="collapsed" desc="The Element Class that maintains state information about the current node">
|
||||
/**
|
||||
* A simple class to maintain information about the current element while parsing
|
||||
* the CPE XML.
|
||||
*/
|
||||
protected class Element {
|
||||
|
||||
/**
|
||||
* A node type in the CPE Schema 2.2
|
||||
*/
|
||||
public static final String CPE_LIST = "cpe-list";
|
||||
/**
|
||||
* A node type in the CPE Schema 2.2
|
||||
*/
|
||||
public static final String CPE_ITEM = "cpe-item";
|
||||
/**
|
||||
* A node type in the CPE Schema 2.2
|
||||
*/
|
||||
public static final String TITLE = "title";
|
||||
/**
|
||||
* A node type in the CPE Schema 2.2
|
||||
*/
|
||||
public static final String NOTES = "notes";
|
||||
/**
|
||||
* A node type in the CPE Schema 2.2
|
||||
*/
|
||||
public static final String NOTE = "note";
|
||||
/**
|
||||
* A node type in the CPE Schema 2.2
|
||||
*/
|
||||
public static final String CHECK = "check";
|
||||
/**
|
||||
* A node type in the CPE Schema 2.2
|
||||
*/
|
||||
public static final String META = "meta:item-metadata";
|
||||
/**
|
||||
* A node type in the CPE Schema 2.2
|
||||
*/
|
||||
public static final String GENERATOR = "generator";
|
||||
/**
|
||||
* A node type in the CPE Schema 2.2
|
||||
*/
|
||||
public static final String PRODUCT_NAME = "product_name";
|
||||
/**
|
||||
* A node type in the CPE Schema 2.2
|
||||
*/
|
||||
public static final String PRODUCT_VERSION = "product_version";
|
||||
/**
|
||||
* A node type in the CPE Schema 2.2
|
||||
*/
|
||||
public static final String SCHEMA_VERSION = "schema_version";
|
||||
/**
|
||||
* A node type in the CPE Schema 2.2
|
||||
*/
|
||||
public static final String TIMESTAMP = "timestamp";
|
||||
private String node = null;
|
||||
|
||||
/**
|
||||
* Gets the value of node
|
||||
*
|
||||
* @return the value of node
|
||||
*/
|
||||
public String getNode() {
|
||||
return this.node;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the value of node
|
||||
*
|
||||
* @param node new value of node
|
||||
*/
|
||||
public void setNode(String node) {
|
||||
this.node = node;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the handler is at the CPE_LIST node
|
||||
*
|
||||
* @return true or false
|
||||
*/
|
||||
public boolean isCpeListNode() {
|
||||
return CPE_LIST.equals(node);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the handler is at the CPE_ITEM node
|
||||
*
|
||||
* @return true or false
|
||||
*/
|
||||
public boolean isCpeItemNode() {
|
||||
return CPE_ITEM.equals(node);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the handler is at the TITLE node
|
||||
*
|
||||
* @return true or false
|
||||
*/
|
||||
public boolean isTitleNode() {
|
||||
return TITLE.equals(node);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the handler is at the NOTES node
|
||||
*
|
||||
* @return true or false
|
||||
*/
|
||||
public boolean isNotesNode() {
|
||||
return NOTES.equals(node);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the handler is at the NOTE node
|
||||
*
|
||||
* @return true or false
|
||||
*/
|
||||
public boolean isNoteNode() {
|
||||
return NOTE.equals(node);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the handler is at the CHECK node
|
||||
*
|
||||
* @return true or false
|
||||
*/
|
||||
public boolean isCheckNode() {
|
||||
return CHECK.equals(node);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the handler is at the META node
|
||||
*
|
||||
* @return true or false
|
||||
*/
|
||||
public boolean isMetaNode() {
|
||||
return META.equals(node);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the handler is at the GENERATOR node
|
||||
*
|
||||
* @return true or false
|
||||
*/
|
||||
public boolean isGeneratorNode() {
|
||||
return GENERATOR.equals(node);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the handler is at the PRODUCT_NAME node
|
||||
*
|
||||
* @return true or false
|
||||
*/
|
||||
public boolean isProductNameNode() {
|
||||
return PRODUCT_NAME.equals(node);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the handler is at the PRODUCT_VERSION node
|
||||
*
|
||||
* @return true or false
|
||||
*/
|
||||
public boolean isProductVersionNode() {
|
||||
return PRODUCT_VERSION.equals(node);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the handler is at the SCHEMA_VERSION node
|
||||
*
|
||||
* @return true or false
|
||||
*/
|
||||
public boolean isSchemaVersionNode() {
|
||||
return SCHEMA_VERSION.equals(node);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the handler is at the TIMESTAMP node
|
||||
*
|
||||
* @return true or false
|
||||
*/
|
||||
public boolean isTimestampNode() {
|
||||
return TIMESTAMP.equals(node);
|
||||
}
|
||||
}
|
||||
// </editor-fold>
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
package org.codesecure.dependencycheck.data.cpe.xml;
|
||||
/*
|
||||
* This file is part of DependencyCheck.
|
||||
*
|
||||
* DependencyCheck is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* DependencyCheck is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with DependencyCheck. If not, see http://www.gnu.org/licenses/.
|
||||
*
|
||||
* Copyright (c) 2012 Jeremy Long. All Rights Reserved.
|
||||
*/
|
||||
|
||||
import org.codesecure.dependencycheck.data.cpe.Entry;
|
||||
import java.io.IOException;
|
||||
import org.apache.lucene.index.CorruptIndexException;
|
||||
|
||||
/**
|
||||
*
|
||||
* An interface used to define the save function used when parsing the CPE XML
|
||||
* file.
|
||||
*
|
||||
* @author Jeremy Long (jeremy.long@gmail.com)
|
||||
*/
|
||||
public interface EntrySaveDelegate {
|
||||
|
||||
/**
|
||||
* Saves a CPE Entry into the Lucene index.
|
||||
*
|
||||
* @param entry a CPE entry.
|
||||
* @throws CorruptIndexException is thrown if the index is corrupt.
|
||||
* @throws IOException is thrown if an IOException occurs.
|
||||
*/
|
||||
void saveEntry(Entry entry) throws CorruptIndexException, IOException;
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
package org.codesecure.dependencycheck.data.cpe.xml;
|
||||
/*
|
||||
* This file is part of DependencyCheck.
|
||||
*
|
||||
* DependencyCheck is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* DependencyCheck is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with DependencyCheck. If not, see http://www.gnu.org/licenses/.
|
||||
*
|
||||
* Copyright (c) 2012 Jeremy Long. All Rights Reserved.
|
||||
*/
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import javax.xml.parsers.ParserConfigurationException;
|
||||
import javax.xml.parsers.SAXParser;
|
||||
import javax.xml.parsers.SAXParserFactory;
|
||||
import org.codesecure.dependencycheck.data.cpe.Indexer;
|
||||
import org.xml.sax.SAXException;
|
||||
|
||||
/**
|
||||
* Imports a CPE XML file into the Lucene CPE Index.
|
||||
*
|
||||
* @author Jeremy Long (jeremy.long@gmail.com)
|
||||
*/
|
||||
public class Importer {
|
||||
|
||||
/**
|
||||
* Private constructor for utility class.
|
||||
*/
|
||||
private Importer() {
|
||||
|
||||
}
|
||||
/**
|
||||
* Imports the CPE XML File into the Lucene Index.
|
||||
*
|
||||
* @param path the path to the CPE XML file.
|
||||
* @throws ParserConfigurationException is thrown if the parser is misconfigured.
|
||||
* @throws SAXException is thrown when there is a SAXException.
|
||||
* @throws IOException is thrown when there is an IOException.
|
||||
*/
|
||||
public static void importXML(String path) throws ParserConfigurationException, SAXException, IOException {
|
||||
SAXParserFactory factory = SAXParserFactory.newInstance();
|
||||
SAXParser saxParser = factory.newSAXParser();
|
||||
CPEHandler handler = new CPEHandler();
|
||||
Indexer indexer = new Indexer();
|
||||
indexer.open();
|
||||
handler.registerSaveDelegate(indexer);
|
||||
File f = new File(path);
|
||||
saxParser.parse(f, handler);
|
||||
indexer.close();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
/**
|
||||
* <html>
|
||||
* <head>
|
||||
* <title>org.codesecure.dependencycheck.data.cpe.xml</title>
|
||||
* </head>
|
||||
* <body>
|
||||
* Contains classes used to parse the CPE XML file.
|
||||
* </body>
|
||||
* </html>
|
||||
*/
|
||||
|
||||
package org.codesecure.dependencycheck.data.cpe.xml;
|
||||
@@ -0,0 +1,12 @@
|
||||
/**
|
||||
* <html>
|
||||
* <head>
|
||||
* <title>org.codesecure.dependencycheck.data</title>
|
||||
* </head>
|
||||
* <body>
|
||||
* Contains utility classes used to work with the Lucene Indexes.
|
||||
* </body>
|
||||
* </html>
|
||||
*/
|
||||
|
||||
package org.codesecure.dependencycheck.data;
|
||||
@@ -0,0 +1,12 @@
|
||||
/**
|
||||
* <html>
|
||||
* <head>
|
||||
* <title>org.codesecure.dependencycheck</title>
|
||||
* </head>
|
||||
* <body>
|
||||
* Includes the main entry point for the DependencyChecker.
|
||||
* </body>
|
||||
* </html>
|
||||
*/
|
||||
|
||||
package org.codesecure.dependencycheck;
|
||||
@@ -0,0 +1,124 @@
|
||||
package org.codesecure.dependencycheck.reporting;
|
||||
/*
|
||||
* This file is part of DependencyCheck.
|
||||
*
|
||||
* DependencyCheck is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* DependencyCheck is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with DependencyCheck. If not, see http://www.gnu.org/licenses/.
|
||||
*
|
||||
* Copyright (c) 2012 Jeremy Long. All Rights Reserved.
|
||||
*/
|
||||
|
||||
import java.io.BufferedWriter;
|
||||
import java.io.File;
|
||||
import java.io.FileWriter;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.InputStreamReader;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import org.apache.velocity.VelocityContext;
|
||||
import org.apache.velocity.app.VelocityEngine;
|
||||
import org.apache.velocity.context.Context;
|
||||
import org.apache.velocity.runtime.RuntimeConstants;
|
||||
import org.apache.velocity.runtime.resource.loader.ClasspathResourceLoader;
|
||||
import org.apache.velocity.tools.ToolManager;
|
||||
import org.apache.velocity.tools.config.EasyFactoryConfiguration;
|
||||
import org.codesecure.dependencycheck.scanner.Dependency;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author Jeremy Long (jeremy.long@gmail.com)
|
||||
*/
|
||||
public class ReportGenerator {
|
||||
|
||||
|
||||
public void generateReports(String outputDir, String applicationName, List<Dependency> dependencies) throws IOException, Exception {
|
||||
|
||||
Map<String, Object> properties = new HashMap<String, Object>();
|
||||
properties.put("dependencies",dependencies);
|
||||
properties.put("applicationName", applicationName);
|
||||
|
||||
String reportName = applicationName.replaceAll("[^a-zA-Z0-9-_ \\.]+", "");
|
||||
String filename = outputDir + File.separatorChar + reportName;
|
||||
generateReport("HtmlReport",filename+".html",properties);
|
||||
//generateReport("XmlReport",filename+".xml",properties);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* much of this code is from http://stackoverflow.com/questions/2931516/loading-velocity-template-inside-a-jar-file
|
||||
* @param templateName the name of the template to load.
|
||||
* @param outFileName The filename and path to write the report to.
|
||||
* @param properties a map of properties to load into the velocity context.
|
||||
* @throws IOException is thrown when the template file does not exist.
|
||||
* @throws Exception is thrown when an exception occurs.
|
||||
*/
|
||||
protected void generateReport(String templateName, String outFileName,
|
||||
Map<String, Object> properties) throws IOException, Exception {
|
||||
|
||||
VelocityEngine ve = new VelocityEngine();
|
||||
ve.setProperty(RuntimeConstants.RESOURCE_LOADER, "classpath");
|
||||
ve.setProperty("classpath.resource.loader.class", ClasspathResourceLoader.class.getName());
|
||||
|
||||
ToolManager manager = new ToolManager();
|
||||
Context context = manager.createContext();
|
||||
EasyFactoryConfiguration config = new EasyFactoryConfiguration();
|
||||
config.addDefaultTools();
|
||||
config.toolbox("application")
|
||||
.tool("esc","org.apache.velocity.tools.generic.EscapeTool")
|
||||
.tool("org.apache.velocity.tools.generic.DateTool");
|
||||
|
||||
manager.configure(config);
|
||||
|
||||
ve.init();
|
||||
|
||||
final String templatePath = "templates/" + templateName + ".vsl";
|
||||
InputStream input = this.getClass().getClassLoader().getResourceAsStream(templatePath);
|
||||
if (input == null) {
|
||||
throw new IOException("Template file doesn't exist");
|
||||
}
|
||||
|
||||
InputStreamReader reader = new InputStreamReader(input);
|
||||
BufferedWriter writer = null;
|
||||
|
||||
//VelocityContext context = new VelocityContext();
|
||||
|
||||
//load the data into the context
|
||||
if (properties != null) {
|
||||
for (Map.Entry<String, Object> property : properties.entrySet()) {
|
||||
context.put(property.getKey(), property.getValue());
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
writer = new BufferedWriter(new FileWriter(new File(outFileName)));
|
||||
|
||||
if (!ve.evaluate(context, writer, templatePath, reader)) {
|
||||
throw new Exception("Failed to convert the template into html.");
|
||||
}
|
||||
writer.flush();
|
||||
} finally {
|
||||
try {
|
||||
writer.close();
|
||||
} catch (Exception ex) {
|
||||
|
||||
}
|
||||
try {
|
||||
reader.close();
|
||||
} catch (Exception ex) {
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
package org.codesecure.dependencycheck.scanner;
|
||||
/*
|
||||
* This file is part of DependencyCheck.
|
||||
*
|
||||
* DependencyCheck is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* DependencyCheck is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with DependencyCheck. If not, see http://www.gnu.org/licenses/.
|
||||
*
|
||||
* Copyright (c) 2012 Jeremy Long. All Rights Reserved.
|
||||
*/
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
|
||||
/**
|
||||
* An interface that defines an Analyzer that is used to identify Dependencies.
|
||||
* An analyzer will collect information about the dependency in the form of
|
||||
* Evidence.
|
||||
*
|
||||
* @author Jeremy Long (jeremy.long@gmail.com)
|
||||
*/
|
||||
public interface Analyzer {
|
||||
|
||||
/**
|
||||
* Loads a specified file and collects data needed to determine any associated CPE/CVE entries.
|
||||
*
|
||||
* @param file path to the file.
|
||||
* @return Dependency the dependency containing evidence needed to determine associated CPE/CVE entries.
|
||||
* @throws IOException is thrown if there is an error reading the dependency file
|
||||
*/
|
||||
Dependency insepct(File file) throws IOException;
|
||||
}
|
||||
@@ -0,0 +1,253 @@
|
||||
package org.codesecure.dependencycheck.scanner;
|
||||
/*
|
||||
* This file is part of DependencyCheck.
|
||||
*
|
||||
* DependencyCheck is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* DependencyCheck is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with DependencyCheck. If not, see http://www.gnu.org/licenses/.
|
||||
*
|
||||
* Copyright (c) 2012 Jeremy Long. All Rights Reserved.
|
||||
*/
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* A program dependency. This object is one of the core components within
|
||||
* DependencyCheck. It is used to collect information about the dependency
|
||||
* in the form of evidence. The Evidence is then used to determine if there
|
||||
* are any known, published, vulnerabilities associated with the program
|
||||
* dependency.
|
||||
*
|
||||
* @author Jeremy Long (jeremy.long@gmail.com)
|
||||
*/
|
||||
public class Dependency {
|
||||
|
||||
/**
|
||||
* The file path of the dependency.
|
||||
*/
|
||||
private String filePath = null;
|
||||
/**
|
||||
* The file name of the dependency.
|
||||
*/
|
||||
private String fileName = null;
|
||||
/**
|
||||
* The md5 hash of the dependency.
|
||||
*/
|
||||
private String md5sum = null;
|
||||
/**
|
||||
* The SHA1 hash of the dependency.
|
||||
*/
|
||||
private String sha1sum = null;
|
||||
/**
|
||||
* A list of CPEs.
|
||||
*/
|
||||
private List<String> cpes = null;
|
||||
/**
|
||||
* A collection of vendor evidence.
|
||||
*/
|
||||
protected EvidenceCollection vendorEvidence = null;
|
||||
/**
|
||||
* A collection of title evidence.
|
||||
*/
|
||||
protected EvidenceCollection titleEvidence = null;
|
||||
/**
|
||||
* A collection of version evidence.
|
||||
*/
|
||||
protected EvidenceCollection versionEvidence = null;
|
||||
|
||||
public Dependency() {
|
||||
vendorEvidence = new EvidenceCollection();
|
||||
titleEvidence = new EvidenceCollection();
|
||||
versionEvidence = new EvidenceCollection();
|
||||
cpes = new ArrayList<String>();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the file name of the JAR.
|
||||
*
|
||||
* @return the file name of the JAR
|
||||
*/
|
||||
public String getFileName() {
|
||||
return this.fileName;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the file name of the JAR.
|
||||
*
|
||||
* @param fileName the file name of the JAR
|
||||
*/
|
||||
public void setFileName(String fileName) {
|
||||
this.fileName = fileName;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the file path of the JAR.
|
||||
* @param filePath the file path of the JAR
|
||||
*/
|
||||
public void setFilePath(String filePath) {
|
||||
this.filePath = filePath;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the file path of the JAR.
|
||||
* @return the file path of the JAR.
|
||||
*/
|
||||
public String getFilePath() {
|
||||
return this.filePath;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the MD5 Checksum of the JAR file.
|
||||
*
|
||||
* @return the MD5 Checksum
|
||||
*/
|
||||
public String getMd5sum() {
|
||||
return this.md5sum;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the MD5 Checksum of the JAR.
|
||||
*
|
||||
* @param md5sum the MD5 Checksum
|
||||
*/
|
||||
public void setMd5sum(String md5sum) {
|
||||
this.md5sum = md5sum;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the SHA1 Checksum of the JAR.
|
||||
*
|
||||
* @return the SHA1 Checksum
|
||||
*/
|
||||
public String getSha1sum() {
|
||||
return this.sha1sum;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the SHA1 Checksum of the JAR.
|
||||
*
|
||||
* @param sha1sum the SHA1 Checksum
|
||||
*/
|
||||
public void setSha1sum(String sha1sum) {
|
||||
this.sha1sum = sha1sum;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a List of possible CPE keys.
|
||||
*
|
||||
* @return an ArrayList containing possible CPE keys.
|
||||
*/
|
||||
public List<String> getCPEs() {
|
||||
return this.cpes;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets a List of possible CPE keys.
|
||||
*
|
||||
* @param cpe A list of CPE values.
|
||||
*/
|
||||
public void setCPEs(List<String> cpe) {
|
||||
this.cpes = cpe;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds an entry to the list of detected CPE keys for the dependency file.
|
||||
*
|
||||
* @param cpe a CPE key for the dependency file
|
||||
*/
|
||||
public void addCPEentry(String cpe) {
|
||||
if (cpes == null) {
|
||||
cpes = new ArrayList<String>();
|
||||
}
|
||||
this.cpes.add(cpe);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the evidence used to identify this dependency.
|
||||
*
|
||||
* @return an EvidenceCollection.
|
||||
*/
|
||||
public EvidenceCollection getEvidence() {
|
||||
return EvidenceCollection.mergeUsed(this.titleEvidence, this.vendorEvidence, this.versionEvidence);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns the evidence used to identify this dependency.
|
||||
*
|
||||
* @return an EvidenceCollection.
|
||||
*/
|
||||
public EvidenceCollection getEvidenceUsed() {
|
||||
EvidenceCollection ec = EvidenceCollection.mergeUsed(this.titleEvidence, this.vendorEvidence, this.versionEvidence);
|
||||
return ec;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the Vendor Evidence.
|
||||
*
|
||||
* @return an EvidenceCollection.
|
||||
*/
|
||||
public EvidenceCollection getVendorEvidence() {
|
||||
return this.vendorEvidence;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the Title Evidence.
|
||||
*
|
||||
* @return an EvidenceCollection.
|
||||
*/
|
||||
public EvidenceCollection getTitleEvidence() {
|
||||
return this.titleEvidence;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the Version Evidence.
|
||||
*
|
||||
* @return an EvidenceCollection.
|
||||
*/
|
||||
public EvidenceCollection getVersionEvidence() {
|
||||
return this.versionEvidence;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines if the specified string was used when searching.
|
||||
*
|
||||
* @param str is the string that is being checked if it was used.
|
||||
* @return true or false.
|
||||
*/
|
||||
public boolean containsUsedString(String str) {
|
||||
if (str == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
String fnd = str.toLowerCase();
|
||||
|
||||
//TODO add the filename is analyzed and added as evidence
|
||||
//TODO remove special characters from filename and check this (including spaces)
|
||||
if (this.fileName != null && this.fileName.contains(fnd)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (vendorEvidence.containsUsedString(str)) {
|
||||
return true;
|
||||
}
|
||||
if (titleEvidence.containsUsedString(str)) {
|
||||
return true;
|
||||
}
|
||||
if (versionEvidence.containsUsedString(fnd)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,177 @@
|
||||
package org.codesecure.dependencycheck.scanner;
|
||||
/*
|
||||
* This file is part of DependencyCheck.
|
||||
*
|
||||
* DependencyCheck is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* DependencyCheck is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with DependencyCheck. If not, see http://www.gnu.org/licenses/.
|
||||
*
|
||||
* Copyright (c) 2012 Jeremy Long. All Rights Reserved.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Evidence is a piece of information about a Dependency.
|
||||
*
|
||||
* @author Jeremy Long (jeremy.long@gmail.com)
|
||||
*/
|
||||
public class Evidence {
|
||||
|
||||
/**
|
||||
* The confidence that the evidence is "high" quality.
|
||||
*/
|
||||
public enum Confidence {
|
||||
/**
|
||||
* High confidence evidence.
|
||||
*/
|
||||
HIGH,
|
||||
/**
|
||||
* Medium confidence evidence.
|
||||
*/
|
||||
MEDIUM,
|
||||
/**
|
||||
* Low confidence evidence.
|
||||
*/
|
||||
LOW
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new Evidence object.
|
||||
*/
|
||||
public Evidence() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new Evidence objects.
|
||||
*
|
||||
* @param source the source of the evidence.
|
||||
* @param name the name of the evidence.
|
||||
* @param value the value of the evidence.
|
||||
* @param confidence the confidence of the evidence.
|
||||
*/
|
||||
public Evidence(String source, String name, String value, Confidence confidence) {
|
||||
this.source = source;
|
||||
this.name = name;
|
||||
this.value = value;
|
||||
this.confidence = confidence;
|
||||
}
|
||||
/**
|
||||
* The name of the evidence.
|
||||
*/
|
||||
protected String name;
|
||||
|
||||
/**
|
||||
* Get the value of name
|
||||
*
|
||||
* @return the value of name
|
||||
*/
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the value of name
|
||||
*
|
||||
* @param name new value of name
|
||||
*/
|
||||
public void setName(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
/**
|
||||
* The source of the evidence.
|
||||
*/
|
||||
protected String source;
|
||||
|
||||
/**
|
||||
* Get the value of source
|
||||
*
|
||||
* @return the value of source
|
||||
*/
|
||||
public String getSource() {
|
||||
return source;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the value of source
|
||||
*
|
||||
* @param source new value of source
|
||||
*/
|
||||
public void setSource(String source) {
|
||||
this.source = source;
|
||||
}
|
||||
/**
|
||||
* The value of the evidence.
|
||||
*/
|
||||
protected String value;
|
||||
|
||||
/**
|
||||
* Get the value of value
|
||||
*
|
||||
* @return the value of value
|
||||
*/
|
||||
public String getValue() {
|
||||
used = true;
|
||||
return value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the value of value
|
||||
*
|
||||
* @param value new value of value
|
||||
*/
|
||||
public void setValue(String value) {
|
||||
this.value = value;
|
||||
}
|
||||
/**
|
||||
* A value indicating if the Evidence has been "used" (aka read).
|
||||
*/
|
||||
protected boolean used;
|
||||
|
||||
/**
|
||||
* Get the value of used
|
||||
*
|
||||
* @return the value of used
|
||||
*/
|
||||
public boolean isUsed() {
|
||||
return used;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the value of used
|
||||
*
|
||||
* @param used new value of used
|
||||
*/
|
||||
public void setUsed(boolean used) {
|
||||
this.used = used;
|
||||
}
|
||||
/**
|
||||
* The confidence level for the evidence.
|
||||
*/
|
||||
protected Confidence confidence;
|
||||
|
||||
/**
|
||||
* Get the value of confidence
|
||||
*
|
||||
* @return the value of confidence
|
||||
*/
|
||||
public Confidence getConfidence() {
|
||||
return confidence;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the value of confidence
|
||||
*
|
||||
* @param confidence new value of confidence
|
||||
*/
|
||||
public void setConfidence(Confidence confidence) {
|
||||
this.confidence = confidence;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,238 @@
|
||||
package org.codesecure.dependencycheck.scanner;
|
||||
/*
|
||||
* This file is part of DependencyCheck.
|
||||
*
|
||||
* DependencyCheck is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* DependencyCheck is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with DependencyCheck. If not, see http://www.gnu.org/licenses/.
|
||||
*
|
||||
* Copyright (c) 2012 Jeremy Long. All Rights Reserved.
|
||||
*/
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import org.codesecure.dependencycheck.utils.Filter;
|
||||
|
||||
/**
|
||||
* Used to maintain a collection of Evidence.
|
||||
*
|
||||
* @author Jeremy Long (jeremy.long@gmail.com)
|
||||
*/
|
||||
public class EvidenceCollection implements Iterable<Evidence> {
|
||||
|
||||
/**
|
||||
* Used to iterate over high confidence evidence contained in the collection.
|
||||
*/
|
||||
private static final Filter<Evidence> HIGH_CONFIDENCE =
|
||||
new Filter<Evidence>() {
|
||||
|
||||
public boolean passes(Evidence evidence) {
|
||||
return evidence.getConfidence() == Evidence.Confidence.HIGH;
|
||||
}
|
||||
};
|
||||
/**
|
||||
* Used to iterate over medium confidence evidence contained in the collection.
|
||||
*/
|
||||
private static final Filter<Evidence> MEDIUM_CONFIDENCE =
|
||||
new Filter<Evidence>() {
|
||||
|
||||
public boolean passes(Evidence evidence) {
|
||||
return evidence.getConfidence() == Evidence.Confidence.MEDIUM;
|
||||
}
|
||||
};
|
||||
/*
|
||||
* Used to iterate over low confidence evidence contained in the collection.
|
||||
*/
|
||||
private static final Filter<Evidence> LOW_CONFIDENCE =
|
||||
new Filter<Evidence>() {
|
||||
|
||||
public boolean passes(Evidence evidence) {
|
||||
return evidence.getConfidence() == Evidence.Confidence.LOW;
|
||||
}
|
||||
};
|
||||
/**
|
||||
* Used to iterate over evidence that has was used (aka read) from the collection.
|
||||
*/
|
||||
private static final Filter<Evidence> EVIDENCE_USED =
|
||||
new Filter<Evidence>() {
|
||||
|
||||
public boolean passes(Evidence evidence) {
|
||||
return evidence.isUsed();
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Used to iterate over evidence of the specified confidence.
|
||||
* @param confidence the confidence level for the evidence to be iterated over.
|
||||
* @return Iterable<Evidence>.
|
||||
*/
|
||||
public final Iterable<Evidence> iterator(Evidence.Confidence confidence) {
|
||||
if (confidence == Evidence.Confidence.HIGH) {
|
||||
return EvidenceCollection.HIGH_CONFIDENCE.filter(this.list);
|
||||
} else if (confidence == Evidence.Confidence.MEDIUM) {
|
||||
return EvidenceCollection.MEDIUM_CONFIDENCE.filter(this.list);
|
||||
} else {
|
||||
return EvidenceCollection.LOW_CONFIDENCE.filter(this.list);
|
||||
}
|
||||
}
|
||||
private List<Evidence> list = null;
|
||||
private List<String> weightedStrings = null;
|
||||
|
||||
/**
|
||||
* Creates a new EvidenceCollection.
|
||||
*/
|
||||
public EvidenceCollection() {
|
||||
list = new ArrayList<Evidence>();
|
||||
weightedStrings = new ArrayList<String>();
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds evidence to the collection.
|
||||
* @param e Evidence.
|
||||
*/
|
||||
public void addEvidence(Evidence e) {
|
||||
list.add(e);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an Evidence object from the parameters and adds the resulting
|
||||
* object to the collection.
|
||||
*
|
||||
* @param source the source of the Evidence.
|
||||
* @param name the name of the Evidence.
|
||||
* @param value the value of the Evidence.
|
||||
* @param confidence the confidence of the Evidence.
|
||||
*/
|
||||
public void addEvidence(String source, String name, String value, Evidence.Confidence confidence) {
|
||||
Evidence e = new Evidence(source, name, value, confidence);
|
||||
addEvidence(e);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds term to the weighting collection. The terms added here are used later
|
||||
* to boost the score of other terms. This is a way of combining evidence from
|
||||
* multiple sources to boost the confidence of the given evidence.
|
||||
*
|
||||
* Example: The term 'Apache' is found in the manifest of a JAR and is added to the
|
||||
* Collection. When we parse the package names within the JAR file we may add
|
||||
* these package names to the "weighted" strings collection to boost the score
|
||||
* in the Lucene query. That way when we construct the Lucene query we find the
|
||||
* term Apache in the collection AND in the weighted strings; as such, we will
|
||||
* boost the confidence of the term Apache.
|
||||
*
|
||||
* @param str to add to the weighting collection.
|
||||
*/
|
||||
public void addWeighting(String str) {
|
||||
if (!weightedStrings.contains(str)) {
|
||||
weightedStrings.add(str);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a list of Weightings - a list of terms that are believed to be of
|
||||
* higher confidence when also found in another location.
|
||||
*
|
||||
* @return List<String>
|
||||
*/
|
||||
public List<String> getWeighting() {
|
||||
return weightedStrings;
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements the iterator interface for the Evidence Collection.
|
||||
* @return an Iterator<Evidence>.
|
||||
*/
|
||||
public Iterator<Evidence> iterator() {
|
||||
return list.iterator();
|
||||
}
|
||||
|
||||
/**
|
||||
* Used to determine if a given string was used (aka read).
|
||||
* @param text the string to search for.
|
||||
* @return whether or not the string was used.
|
||||
*/
|
||||
public boolean containsUsedString(String text) {
|
||||
if (text == null) {
|
||||
return false;
|
||||
}
|
||||
text = text.toLowerCase();
|
||||
|
||||
for (Evidence e : this.list) {
|
||||
if (e.used && e.value.contains(text)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether or not the collection contains evidence of a specified
|
||||
* Confidence.
|
||||
* @param confidence A Confidence value.
|
||||
* @return boolean.
|
||||
*/
|
||||
public boolean contains(Evidence.Confidence confidence) {
|
||||
for (Evidence e : list) {
|
||||
if (e.confidence == confidence) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Merges multiple EvidenceCollections together, only merging evidence that
|
||||
* was used, into a new EvidenceCollection.
|
||||
*
|
||||
* @param ec One or more EvidenceCollections.
|
||||
* @return a new EvidenceCollection containing the used evidence.
|
||||
*/
|
||||
public static EvidenceCollection mergeUsed(EvidenceCollection... ec) {
|
||||
EvidenceCollection ret = new EvidenceCollection();
|
||||
for (EvidenceCollection col : ec) {
|
||||
for (Evidence e : col.list) {
|
||||
if (e.isUsed()) {
|
||||
ret.addEvidence(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a string of evidence 'values' for a given confidence.
|
||||
* @param confidence the confidence filter applied to the toString method.
|
||||
* @return a string containing the evidence.
|
||||
*/
|
||||
public String toString(Evidence.Confidence confidence) {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
for (Evidence e : this.iterator(confidence)) {
|
||||
sb.append(e.getValue()).append(' ');
|
||||
}
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a string of evidence 'values'.
|
||||
* @return a string containing the evidence.
|
||||
*/
|
||||
@Override
|
||||
public String toString() {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
for (Evidence e : this.list) {
|
||||
sb.append(e.getValue()).append(' ');
|
||||
}
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,365 @@
|
||||
package org.codesecure.dependencycheck.scanner;
|
||||
/*
|
||||
* This file is part of DependencyCheck.
|
||||
*
|
||||
* DependencyCheck is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* DependencyCheck is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with DependencyCheck. If not, see http://www.gnu.org/licenses/.
|
||||
*
|
||||
* Copyright (c) 2012 Jeremy Long. All Rights Reserved.
|
||||
*/
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.IOException;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.util.HashMap;
|
||||
import java.util.jar.Attributes;
|
||||
import java.util.jar.JarFile;
|
||||
import java.util.jar.Manifest;
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
import java.util.regex.Pattern;
|
||||
import org.codesecure.dependencycheck.utils.Checksum;
|
||||
|
||||
/**
|
||||
*
|
||||
* Used to load a JAR file and collect information that can be used to determine the associated CPE.
|
||||
*
|
||||
* <!--
|
||||
* ideas - scan the JAR to see if there is a "version" final static string?
|
||||
* scan manifest for version info
|
||||
* get md5 and sh1 checksums to lookup file via maven centrals hosted md5sum files
|
||||
* examine file name itself for version info
|
||||
* -->
|
||||
*
|
||||
* @author Jeremy Long (jeremy.long@gmail.com)
|
||||
*/
|
||||
public class JarAnalyzer implements Analyzer {
|
||||
|
||||
private static final String BUNDLE_VERSION = "Bundle-Version"; //: 2.1.2
|
||||
private static final String BUNDLE_DESCRIPTION = "Bundle-Description"; //: Apache Struts 2
|
||||
private static final String BUNDLE_NAME = "Bundle-Name"; //: Struts 2 Core
|
||||
private static final String BUNDLE_VENDOR = "Bundle-Vendor"; //: Apache Software Foundation
|
||||
|
||||
|
||||
private enum STRING_STATE {
|
||||
ALPHA,
|
||||
NUMBER,
|
||||
OTHER
|
||||
}
|
||||
private STRING_STATE determineState(char c) {
|
||||
if (c>='0' && c<='9' || c=='.') {
|
||||
return STRING_STATE.NUMBER;
|
||||
} else if (c>='a' && c<='z') {
|
||||
return STRING_STATE.ALPHA;
|
||||
} else {
|
||||
return STRING_STATE.OTHER;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads a specified JAR file and collects information from the manifest and
|
||||
* checksums to identify the correct CPE information.
|
||||
*
|
||||
* @param file path to the JAR file.
|
||||
* @return a dependency derived for the specified file.
|
||||
* @throws IOException is thrown if there is an error reading the JAR file.
|
||||
*/
|
||||
public Dependency insepct(File file) throws IOException {
|
||||
|
||||
Dependency dependency = new Dependency();
|
||||
|
||||
String fileName = file.getName();
|
||||
dependency.setFileName(fileName);
|
||||
dependency.setFilePath(file.getCanonicalPath());
|
||||
String fileNameEvidence = fileName.substring(0,fileName.length()-4)
|
||||
.toLowerCase()
|
||||
.replace('-', ' ')
|
||||
.replace('_', ' ');
|
||||
StringBuilder sb = new StringBuilder(fileNameEvidence.length());
|
||||
STRING_STATE state = determineState(fileNameEvidence.charAt(0));
|
||||
|
||||
for(int i=0;i<fileNameEvidence.length();i++) {
|
||||
char c = fileNameEvidence.charAt(i);
|
||||
STRING_STATE new_state = determineState(c);
|
||||
if (new_state != state) {
|
||||
sb.append(' ');
|
||||
state = new_state;
|
||||
}
|
||||
sb.append(c);
|
||||
}
|
||||
Pattern rx = Pattern.compile("\\s\\s+");
|
||||
fileNameEvidence = rx.matcher(sb.toString()).replaceAll(" ");
|
||||
dependency.getTitleEvidence().addEvidence("jar", "file name",
|
||||
fileNameEvidence, Evidence.Confidence.HIGH);
|
||||
dependency.getVendorEvidence().addEvidence("jar", "file name",
|
||||
fileNameEvidence, Evidence.Confidence.HIGH);
|
||||
dependency.getVersionEvidence().addEvidence("jar", "file name",
|
||||
fileNameEvidence, Evidence.Confidence.HIGH);
|
||||
|
||||
String md5 = null;
|
||||
String sha1 = null;
|
||||
try {
|
||||
md5 = Checksum.getMD5Checksum(file);
|
||||
sha1 = Checksum.getSHA1Checksum(file);
|
||||
} catch (FileNotFoundException ex) {
|
||||
Logger.getLogger(JarAnalyzer.class.getName()).log(Level.SEVERE, null, ex);
|
||||
} catch (NoSuchAlgorithmException ex) {
|
||||
Logger.getLogger(JarAnalyzer.class.getName()).log(Level.SEVERE, null, ex);
|
||||
}
|
||||
dependency.setMd5sum(md5);
|
||||
dependency.setSha1sum(sha1);
|
||||
|
||||
parseManifest(dependency);
|
||||
analyzePackageNames(dependency);
|
||||
|
||||
//TODO - can we get "version" information from the filename? add it as medium confidence?
|
||||
// strip extension. find first numeric, chop off the first part. consider replacing [_-] with .
|
||||
//dependency.getVersionEvidence().addEvidence("jar", "file name",
|
||||
// version from file, Evidence.Confidence.MEDIUM);
|
||||
|
||||
return dependency;
|
||||
}
|
||||
|
||||
/**
|
||||
* Analyzes the path information of the classes contained within the JarAnalyzer
|
||||
* to try and determine possible vendor or product names. If any are found they are
|
||||
* stored in the packageVendor and packageProduct hashSets.
|
||||
*
|
||||
* @param dependency A reference to the dependency.
|
||||
* @throws IOException is thrown if there is an error reading the JAR file.
|
||||
*/
|
||||
protected void analyzePackageNames(Dependency dependency) throws IOException {
|
||||
|
||||
JarFile jar = new JarFile(dependency.getFilePath());
|
||||
java.util.Enumeration en = jar.entries();
|
||||
|
||||
HashMap<String, Integer> level0 = new HashMap<String, Integer>();
|
||||
HashMap<String, Integer> level1 = new HashMap<String, Integer>();
|
||||
HashMap<String, Integer> level2 = new HashMap<String, Integer>();
|
||||
HashMap<String, Integer> level3 = new HashMap<String, Integer>();
|
||||
int count = 0;
|
||||
while (en.hasMoreElements()) {
|
||||
java.util.jar.JarEntry entry = (java.util.jar.JarEntry) en.nextElement();
|
||||
if (entry.getName().endsWith(".class") && entry.getName().contains("/")) {
|
||||
String[] path = entry.getName().toLowerCase().split("/");
|
||||
|
||||
if ("java".equals(path[0])
|
||||
|| "javax".equals(path[0])
|
||||
|| ("com".equals(path[0]) && "sun".equals(path[0]))) {
|
||||
continue;
|
||||
}
|
||||
|
||||
count += 1;
|
||||
String temp = path[0];
|
||||
if (level0.containsKey(temp)) {
|
||||
level0.put(temp, level0.get(temp) + 1);
|
||||
} else {
|
||||
level0.put(temp, 1);
|
||||
}
|
||||
|
||||
if (path.length > 2) {
|
||||
temp += "/" + path[1];
|
||||
if (level1.containsKey(temp)) {
|
||||
level1.put(temp, level1.get(temp) + 1);
|
||||
} else {
|
||||
level1.put(temp, 1);
|
||||
}
|
||||
}
|
||||
if (path.length > 3) {
|
||||
temp += "/" + path[2];
|
||||
if (level2.containsKey(temp)) {
|
||||
level2.put(temp, level2.get(temp) + 1);
|
||||
} else {
|
||||
level2.put(temp, 1);
|
||||
}
|
||||
}
|
||||
|
||||
if (path.length > 4) {
|
||||
temp += "/" + path[3];
|
||||
if (level3.containsKey(temp)) {
|
||||
level3.put(temp, level3.get(temp) + 1);
|
||||
} else {
|
||||
level3.put(temp, 1);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
if (count == 0) {
|
||||
return;
|
||||
}
|
||||
EvidenceCollection vendor = dependency.getVendorEvidence();
|
||||
EvidenceCollection title = dependency.getTitleEvidence();
|
||||
|
||||
for (String s : level0.keySet()) {
|
||||
if (!"org".equals(s) && !"com".equals(s)) {
|
||||
vendor.addWeighting(s);
|
||||
title.addWeighting(s);
|
||||
vendor.addEvidence("jar", "package", s, Evidence.Confidence.LOW);
|
||||
title.addEvidence("jar", "package", s, Evidence.Confidence.LOW);
|
||||
}
|
||||
}
|
||||
for (String s : level1.keySet()) {
|
||||
float ratio = level1.get(s);
|
||||
ratio /= count;
|
||||
if (ratio > 0.5) {
|
||||
String[] parts = s.split("/");
|
||||
if ("org".equals(parts[0]) || "com".equals(parts[0])) {
|
||||
vendor.addWeighting(parts[1]);
|
||||
vendor.addEvidence("jar", "package", parts[1], Evidence.Confidence.LOW);
|
||||
} else {
|
||||
vendor.addWeighting(parts[0]);
|
||||
title.addWeighting(parts[1]);
|
||||
vendor.addEvidence("jar", "package", parts[0], Evidence.Confidence.LOW);
|
||||
title.addEvidence("jar", "package", parts[1], Evidence.Confidence.LOW);
|
||||
}
|
||||
}
|
||||
}
|
||||
for (String s : level2.keySet()) {
|
||||
float ratio = level2.get(s);
|
||||
ratio /= count;
|
||||
if (ratio > 0.4) {
|
||||
String[] parts = s.split("/");
|
||||
if ("org".equals(parts[0]) || "com".equals(parts[0])) {
|
||||
vendor.addWeighting(parts[1]);
|
||||
title.addWeighting(parts[2]);
|
||||
vendor.addEvidence("jar", "package", parts[1], Evidence.Confidence.LOW);
|
||||
title.addEvidence("jar", "package", parts[2], Evidence.Confidence.LOW);
|
||||
} else {
|
||||
vendor.addWeighting(parts[0]);
|
||||
vendor.addWeighting(parts[1]);
|
||||
title.addWeighting(parts[1]);
|
||||
title.addWeighting(parts[2]);
|
||||
vendor.addEvidence("jar", "package", parts[0], Evidence.Confidence.LOW);
|
||||
vendor.addEvidence("jar", "package", parts[1], Evidence.Confidence.LOW);
|
||||
title.addEvidence("jar", "package", parts[1], Evidence.Confidence.LOW);
|
||||
title.addEvidence("jar", "package", parts[2], Evidence.Confidence.LOW);
|
||||
}
|
||||
}
|
||||
}
|
||||
for (String s : level3.keySet()) {
|
||||
float ratio = level3.get(s);
|
||||
ratio /= count;
|
||||
if (ratio > 0.3) {
|
||||
String[] parts = s.split("/");
|
||||
if ("org".equals(parts[0]) || "com".equals(parts[0])) {
|
||||
vendor.addWeighting(parts[1]);
|
||||
vendor.addWeighting(parts[2]);
|
||||
title.addWeighting(parts[2]);
|
||||
title.addWeighting(parts[3]);
|
||||
vendor.addEvidence("jar", "package", parts[1], Evidence.Confidence.LOW);
|
||||
vendor.addEvidence("jar", "package", parts[2], Evidence.Confidence.LOW);
|
||||
title.addEvidence("jar", "package", parts[2], Evidence.Confidence.LOW);
|
||||
title.addEvidence("jar", "package", parts[3], Evidence.Confidence.LOW);
|
||||
|
||||
} else {
|
||||
vendor.addWeighting(parts[0]);
|
||||
vendor.addWeighting(parts[1]);
|
||||
vendor.addWeighting(parts[2]);
|
||||
title.addWeighting(parts[1]);
|
||||
title.addWeighting(parts[2]);
|
||||
title.addWeighting(parts[3]);
|
||||
vendor.addEvidence("jar", "package", parts[0], Evidence.Confidence.LOW);
|
||||
vendor.addEvidence("jar", "package", parts[1], Evidence.Confidence.LOW);
|
||||
vendor.addEvidence("jar", "package", parts[2], Evidence.Confidence.LOW);
|
||||
title.addEvidence("jar", "package", parts[1], Evidence.Confidence.LOW);
|
||||
title.addEvidence("jar", "package", parts[2], Evidence.Confidence.LOW);
|
||||
title.addEvidence("jar", "package", parts[3], Evidence.Confidence.LOW);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Reads the manifest from the JAR file and collects the:</p>
|
||||
* <ul><li>Implementation Title</li>
|
||||
* <li>Implementation Version</li>
|
||||
* <li>Implementation Vendor</li>
|
||||
* <li>Implementation VendorId</li>
|
||||
* <li>Bundle Name</li>
|
||||
* <li>Bundle Version</li>
|
||||
* <li>Bundle Vendor</li>
|
||||
* <li>Bundle Description</li>
|
||||
* <li>Main Class</li>
|
||||
* </ul>
|
||||
*
|
||||
* @param dependency A reference to the dependency.
|
||||
* @throws IOException if there is an issue reading the JAR file.
|
||||
*/
|
||||
protected void parseManifest(Dependency dependency) throws IOException {
|
||||
JarFile jar = new JarFile(dependency.getFilePath());
|
||||
Manifest manifest = jar.getManifest();
|
||||
Attributes atts = manifest.getMainAttributes();
|
||||
|
||||
EvidenceCollection vendorEvidence = dependency.getVendorEvidence();
|
||||
EvidenceCollection titleEvidence = dependency.getTitleEvidence();
|
||||
EvidenceCollection versionEvidence = dependency.getVendorEvidence();
|
||||
|
||||
String source = "Manifest";
|
||||
String name = Attributes.Name.IMPLEMENTATION_TITLE.toString();
|
||||
String value = atts.getValue(Attributes.Name.IMPLEMENTATION_TITLE);
|
||||
if (value != null) {
|
||||
titleEvidence.addEvidence(source, name, value, Evidence.Confidence.HIGH);
|
||||
}
|
||||
|
||||
name = Attributes.Name.IMPLEMENTATION_VERSION.toString();
|
||||
value = atts.getValue(Attributes.Name.IMPLEMENTATION_VERSION);
|
||||
if (value != null) {
|
||||
versionEvidence.addEvidence(source, name, value, Evidence.Confidence.HIGH);
|
||||
}
|
||||
|
||||
name = Attributes.Name.IMPLEMENTATION_VENDOR.toString();
|
||||
value = atts.getValue(Attributes.Name.IMPLEMENTATION_VENDOR);
|
||||
if (value != null) {
|
||||
vendorEvidence.addEvidence(source, name, value, Evidence.Confidence.HIGH);
|
||||
}
|
||||
|
||||
name = Attributes.Name.IMPLEMENTATION_VENDOR_ID.toString();
|
||||
value = atts.getValue(Attributes.Name.IMPLEMENTATION_VENDOR_ID);
|
||||
if (value != null) {
|
||||
vendorEvidence.addEvidence(source, name, value, Evidence.Confidence.MEDIUM);
|
||||
}
|
||||
|
||||
name = BUNDLE_DESCRIPTION;
|
||||
value = atts.getValue(BUNDLE_DESCRIPTION);
|
||||
if (value != null) {
|
||||
titleEvidence.addEvidence(source, name, value, Evidence.Confidence.MEDIUM);
|
||||
}
|
||||
|
||||
name = BUNDLE_VENDOR;
|
||||
value = atts.getValue(BUNDLE_VENDOR);
|
||||
if (value != null) {
|
||||
vendorEvidence.addEvidence(source, name, value, Evidence.Confidence.MEDIUM);
|
||||
}
|
||||
|
||||
name = BUNDLE_VERSION;
|
||||
value = atts.getValue(BUNDLE_VERSION);
|
||||
if (value != null) {
|
||||
versionEvidence.addEvidence(source, name, value, Evidence.Confidence.MEDIUM);
|
||||
}
|
||||
name = BUNDLE_NAME;
|
||||
value = atts.getValue(BUNDLE_NAME);
|
||||
if (value != null) {
|
||||
titleEvidence.addEvidence(source, name, value, Evidence.Confidence.LOW);
|
||||
}
|
||||
|
||||
name = Attributes.Name.MAIN_CLASS.toString();
|
||||
value = atts.getValue(Attributes.Name.MAIN_CLASS);
|
||||
if (value != null) {
|
||||
titleEvidence.addEvidence(source, name, value, Evidence.Confidence.MEDIUM);
|
||||
vendorEvidence.addEvidence(source, name, value, Evidence.Confidence.MEDIUM);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,206 @@
|
||||
package org.codesecure.dependencycheck.scanner;
|
||||
/*
|
||||
* This file is part of DependencyCheck.
|
||||
*
|
||||
* DependencyCheck is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* DependencyCheck is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with DependencyCheck. If not, see http://www.gnu.org/licenses/.
|
||||
*
|
||||
* Copyright (c) 2012 Jeremy Long. All Rights Reserved.
|
||||
*/
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
import org.codesecure.dependencycheck.utils.Settings;
|
||||
import org.codesecure.dependencycheck.utils.Settings.KEYS;
|
||||
|
||||
/**
|
||||
* Scans files, directories, etc. for Dependencies. Analyzers are loaded and
|
||||
* used to process the files found by the scanner, if a file is encountered and
|
||||
* an Analyzer is associated with the file type then the file is turned into a
|
||||
* dependency by the Analyzer.
|
||||
*
|
||||
* @author Jeremy Long (jeremy.long@gmail.com)
|
||||
*/
|
||||
public class Scanner {
|
||||
|
||||
/**
|
||||
* The list of dependencies.
|
||||
*/
|
||||
protected List<Dependency> dependencies = new ArrayList<Dependency>();
|
||||
/**
|
||||
* A Map of analyzers - the key is the file extension.
|
||||
*/
|
||||
protected Map<String, Analyzer> analyzers = new HashMap<String, Analyzer>();
|
||||
|
||||
/**
|
||||
* Creates a new Scanner.
|
||||
*/
|
||||
public Scanner() {
|
||||
loadAnalyzers();
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads the analyzers specified in the configuration file (or system properties).
|
||||
*/
|
||||
private void loadAnalyzers() {
|
||||
Map<String, String> associations = Settings.getPropertiesByPrefix(KEYS.FILE_EXTENSION_ANALYZER_ASSOCIATION_PREFIX);
|
||||
for (Map.Entry<String, String> entry : associations.entrySet()) {
|
||||
addAnalyzer(entry.getKey(), entry.getValue());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds an Analyzer to the collection of analyzers and associates the
|
||||
* analyzer with a file extension.
|
||||
*
|
||||
* If the specified class does not implement 'org.codesecure.dependencycheck.detect.Analyzer'
|
||||
* the load will fail mostly silently - only writting the failure to the log file.
|
||||
*
|
||||
* @param extension the file extension that this analyzer can analyze.
|
||||
* @param className the fully qualified classname of the Analyzer.
|
||||
*/
|
||||
public final void addAnalyzer(String extension, String className) {
|
||||
|
||||
ClassLoader loader = this.getClass().getClassLoader();
|
||||
try {
|
||||
Class analyzer = loader.loadClass(className);
|
||||
boolean implmnts = false;
|
||||
for (Class p : analyzer.getInterfaces()) {
|
||||
if (org.codesecure.dependencycheck.scanner.Analyzer.class.isAssignableFrom(p)) {
|
||||
implmnts = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (implmnts) {
|
||||
this.analyzers.put(extension, (Analyzer) analyzer.newInstance());
|
||||
} else {
|
||||
String msg = String.format("Class '%s' does not implement org.codesecure.dependencycheck.scanner.Analyzer and cannot be loaded as an Analyzer for extension '%s'.", className, extension);
|
||||
Logger.getLogger(Scanner.class.getName()).log(Level.WARNING, msg);
|
||||
}
|
||||
} catch (ClassNotFoundException ex) {
|
||||
Logger.getLogger(Scanner.class.getName()).log(Level.SEVERE, null, ex);
|
||||
} catch (InstantiationException ex) {
|
||||
Logger.getLogger(Scanner.class.getName()).log(Level.SEVERE, null, ex);
|
||||
} catch (IllegalAccessException ex) {
|
||||
Logger.getLogger(Scanner.class.getName()).log(Level.SEVERE, null, ex);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the Map of the analyzers.
|
||||
*
|
||||
* @return the analyzers loaded
|
||||
*/
|
||||
public Map<String, Analyzer> getAnalyzers() {
|
||||
return analyzers;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the dependencies identified
|
||||
*
|
||||
* @return the dependencies identified
|
||||
*/
|
||||
public List<Dependency> getDependencies() {
|
||||
return dependencies;
|
||||
}
|
||||
|
||||
/**
|
||||
* Scans a given file or directory. If a directory is specified, it will be
|
||||
* scanned recursively.
|
||||
* Any dependencies identified are added to the dependency collection.
|
||||
*
|
||||
* @param path the path to a file or directory to be analyzed.
|
||||
*/
|
||||
public void scan(String path) {
|
||||
File file = new File(path);
|
||||
if (file.exists()) {
|
||||
if (file.isDirectory()) {
|
||||
scanDirectory(file);
|
||||
} else {
|
||||
scanFile(file);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Recursively scans files and directories.
|
||||
* Any dependencies identified are added to the dependency collection.
|
||||
*
|
||||
* @param dir the directory to scan.
|
||||
*/
|
||||
protected void scanDirectory(File dir) {
|
||||
File[] files = dir.listFiles();
|
||||
for (File f : files) {
|
||||
if (f.isDirectory()) {
|
||||
scanDirectory(f);
|
||||
} else {
|
||||
scanFile(f);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Scans a specified file. If a dependency is identified it is added to the
|
||||
* dependency collection.
|
||||
*
|
||||
* @param file The file to scan.
|
||||
*/
|
||||
protected void scanFile(File file) {
|
||||
if (!file.isFile()) {
|
||||
String msg = String.format("Path passed to scanFile(File) is not a file: %s.", file.toString());
|
||||
Logger.getLogger(Scanner.class.getName()).log(Level.WARNING, msg);
|
||||
}
|
||||
String fileName = file.getName();
|
||||
String extension = getFileExtension(fileName);
|
||||
if (extension != null) {
|
||||
if (analyzers.containsKey(extension)) {
|
||||
Analyzer a = analyzers.get(extension);
|
||||
try {
|
||||
Dependency dependency = a.insepct(file);
|
||||
dependencies.add(dependency);
|
||||
} catch (IOException ex) {
|
||||
String msg = String.format("IOException occured while scanning the file '%s'.", file.toString());
|
||||
Logger.getLogger(Scanner.class.getName()).log(Level.SEVERE, msg, ex);
|
||||
}
|
||||
} else {
|
||||
String msg = String.format("No analyzer is configured for files of type '%s'. The file, '%s', was not analyzed.", extension, file.toString());
|
||||
Logger.getLogger(Scanner.class.getName()).log(Level.WARNING, msg);
|
||||
}
|
||||
} else {
|
||||
String msg = String.format("No files extension found on file '%s'. The file was not analyzed.", file.toString());
|
||||
Logger.getLogger(Scanner.class.getName()).log(Level.WARNING, msg);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the file extension for a specified file.
|
||||
* @param fileName the file name to retrieve the file extension from.
|
||||
* @return the file extension.
|
||||
*/
|
||||
protected String getFileExtension(String fileName) {
|
||||
String ret = null;
|
||||
int pos = fileName.lastIndexOf(".");
|
||||
if (pos >= 0) {
|
||||
ret = fileName.substring(pos + 1, fileName.length());
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
/**
|
||||
* <html>
|
||||
* <head>
|
||||
* <title>org.codesecure.dependencycheck.scanner</title>
|
||||
* </head>
|
||||
* <body>
|
||||
* The scanner package contains the utilities to scan files and directories for
|
||||
* dependencies. Analyzers are used to inspect the identified dependencies and
|
||||
* collect Evidence. This evidence is then used to determine if the dependency
|
||||
* has a known CPE.
|
||||
* </body>
|
||||
* </html>
|
||||
*/
|
||||
|
||||
package org.codesecure.dependencycheck.scanner;
|
||||
104
src/main/java/org/codesecure/dependencycheck/utils/Checksum.java
Normal file
104
src/main/java/org/codesecure/dependencycheck/utils/Checksum.java
Normal file
@@ -0,0 +1,104 @@
|
||||
package org.codesecure.dependencycheck.utils;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.security.MessageDigest;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
/**
|
||||
* Includes methods to generate the MD5 and SHA1 checksum.
|
||||
*
|
||||
* This code was copied from Real's How To. It has been slightly modified.
|
||||
*
|
||||
* Written and compiled by Réal Gagnon ©1998-2012
|
||||
*
|
||||
* @author Real's How To: http://www.rgagnon.com/javadetails/java-0416.html
|
||||
*
|
||||
*/
|
||||
public class Checksum {
|
||||
|
||||
/**
|
||||
* <p>Creates the cryptographic checksum of a given file using the specified alogirhtm.</p>
|
||||
* <p>This algorithm was copied and heavily modified from Real's How To: http://www.rgagnon.com/javadetails/java-0416.html</p>
|
||||
*
|
||||
* @param algorithm the algorithm to use to calculate the checksum
|
||||
* @param file the file to calculate the checksum for
|
||||
* @return the checksum
|
||||
* @throws FileNotFoundException when the file does not exist
|
||||
* @throws NoSuchAlgorithmException when an algorithm is specified that does not exist
|
||||
*/
|
||||
public static byte[] getChecksum(String algorithm, File file) throws FileNotFoundException, NoSuchAlgorithmException {
|
||||
InputStream fis = new FileInputStream(file);
|
||||
byte[] buffer = new byte[1024];
|
||||
MessageDigest complete = MessageDigest.getInstance(algorithm);
|
||||
int numRead;
|
||||
try {
|
||||
do {
|
||||
numRead = fis.read(buffer);
|
||||
if (numRead > 0) {
|
||||
complete.update(buffer, 0, numRead);
|
||||
}
|
||||
} while (numRead != -1);
|
||||
} catch (IOException ex) {
|
||||
Logger.getLogger(Checksum.class.getName()).log(Level.SEVERE, null, ex);
|
||||
} finally {
|
||||
try {
|
||||
fis.close();
|
||||
} catch (IOException ex) {
|
||||
Logger.getLogger(Checksum.class.getName()).log(Level.SEVERE, null, ex);
|
||||
}
|
||||
}
|
||||
return complete.digest();
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculates the MD5 checksum of a specified file.
|
||||
*
|
||||
* @param file the file to generate the MD5 checksum
|
||||
* @return the hex representation of the MD5 hash
|
||||
* @throws FileNotFoundException when the file passed in does not exist
|
||||
* @throws NoSuchAlgorithmException when the MD5 algorithm is not available
|
||||
*/
|
||||
public static String getMD5Checksum(File file) throws FileNotFoundException, NoSuchAlgorithmException {
|
||||
byte[] b = getChecksum("MD5", file);
|
||||
return getHex(b);
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculates the SHA1 checksum of a specified file.
|
||||
*
|
||||
* @param file the file to generate the MD5 checksum
|
||||
* @return the hex representation of the SHA1 hash
|
||||
* @throws FileNotFoundException when the file passed in does not exist
|
||||
* @throws NoSuchAlgorithmException when the SHA1 algorithm is not available
|
||||
*/
|
||||
public static String getSHA1Checksum(File file) throws FileNotFoundException, NoSuchAlgorithmException {
|
||||
byte[] b = getChecksum("SHA1", file);
|
||||
return getHex(b);
|
||||
}
|
||||
private static final String HEXES = "0123456789ABCDEF";
|
||||
|
||||
/**
|
||||
* <p>Converts a byte array into a hex string.</p>
|
||||
*
|
||||
* <p>This method was copied from <a href="http://www.rgagnon.com/javadetails/java-0596.html">http://www.rgagnon.com/javadetails/java-0596.html</a></p>
|
||||
*
|
||||
* @param raw a byte array
|
||||
* @return the hex representation of the byte array
|
||||
*/
|
||||
public static String getHex(byte[] raw) {
|
||||
if (raw == null) {
|
||||
return null;
|
||||
}
|
||||
final StringBuilder hex = new StringBuilder(2 * raw.length);
|
||||
for (final byte b : raw) {
|
||||
hex.append(HEXES.charAt((b & 0xF0) >> 4)).append(HEXES.charAt((b & 0x0F)));
|
||||
}
|
||||
return hex.toString();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,305 @@
|
||||
package org.codesecure.dependencycheck.utils;
|
||||
/*
|
||||
* This file is part of DependencyCheck.
|
||||
*
|
||||
* DependencyCheck is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* DependencyCheck is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with DependencyCheck. If not, see http://www.gnu.org/licenses/.
|
||||
*
|
||||
* Copyright (c) 2012 Jeremy Long. All Rights Reserved.
|
||||
*/
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.net.URL;
|
||||
import java.net.URLClassLoader;
|
||||
import java.util.jar.Attributes;
|
||||
import java.util.jar.Manifest;
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
import org.apache.commons.cli.CommandLine;
|
||||
import org.apache.commons.cli.CommandLineParser;
|
||||
import org.apache.commons.cli.HelpFormatter;
|
||||
import org.apache.commons.cli.Option;
|
||||
import org.apache.commons.cli.OptionBuilder;
|
||||
import org.apache.commons.cli.OptionGroup;
|
||||
import org.apache.commons.cli.Options;
|
||||
import org.apache.commons.cli.ParseException;
|
||||
import org.apache.commons.cli.PosixParser;
|
||||
|
||||
/**
|
||||
* A utility to parse command line arguments for the DependencyCheck.
|
||||
*
|
||||
* @author Jeremy Long (jeremy.long@gmail.com)
|
||||
*/
|
||||
public final class CliParser {
|
||||
|
||||
/**
|
||||
* The command line.
|
||||
*/
|
||||
private CommandLine line = null;
|
||||
/**
|
||||
* The options for the command line parser.
|
||||
*/
|
||||
private Options options = createCommandLineOptions();
|
||||
/**
|
||||
* indicates whether the arguments are valid.
|
||||
*/
|
||||
boolean isValid = true;
|
||||
|
||||
/**
|
||||
* Parses the arguments passed in and captures the results for later use.
|
||||
*
|
||||
* @param args the command line arguments
|
||||
* @throws FileNotFoundException is thrown when a 'file' argument does not
|
||||
* point to a file that exists.
|
||||
* @throws ParseException is thrown when a Parse Exception occurs.
|
||||
*/
|
||||
public void parse(String[] args) throws FileNotFoundException, ParseException {
|
||||
line = parseArgs(args);
|
||||
|
||||
if (line != null) {
|
||||
validateArgs();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses the command line arguments.
|
||||
*
|
||||
* @param args the command line arguments
|
||||
* @return the results of parsing the command line arguments
|
||||
* @throws ParseException if the arguments are invalid
|
||||
*/
|
||||
private CommandLine parseArgs(String[] args) throws ParseException {
|
||||
CommandLineParser parser = new PosixParser();
|
||||
CommandLine ln = parser.parse(options, args);
|
||||
return ln;
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates that the command line arguments are valid.
|
||||
*
|
||||
* @throws FileNotFoundException if there is a file specified by either the
|
||||
* SCAN or CPE command line arguments that does not exist.
|
||||
*/
|
||||
private void validateArgs() throws FileNotFoundException, ParseException {
|
||||
if (isLoadCPE()) {
|
||||
validatePathExists(getCpeFile());
|
||||
}
|
||||
if (isRunScan()) {
|
||||
validatePathExists(getScanFiles());
|
||||
if (!line.hasOption(ArgumentName.OUT)) {
|
||||
//TODO - need a new exception type here, this isn't really a parseexception.
|
||||
throw new ParseException("Scan cannot be run without specifying a directory to write the reports to via the 'out' argument.");
|
||||
} else {
|
||||
String p = line.getOptionValue(ArgumentName.OUT,"");
|
||||
File f = new File(p);
|
||||
if ("".equals(p) || !(f.exists() && f.isDirectory())) {
|
||||
//TODO - need a new exception type here, this isn't really a parseexception.
|
||||
throw new ParseException("A valid directory name must be specified for the 'out' argument.");
|
||||
}
|
||||
}
|
||||
if (!line.hasOption(ArgumentName.APPNAME)) {
|
||||
throw new ParseException("Scan cannot be run without specifying an application name via the 'app' argument.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates whether or not the path(s) points at a file that exists; if the
|
||||
* path(s) does not point to an existing file a FileNotFoundException is thrown.
|
||||
*
|
||||
* @param paths the paths to validate if they exists
|
||||
* @throws FileNoteFoundException is thrown if one of the paths being validated does not exist.
|
||||
*/
|
||||
private void validatePathExists(String[] paths) throws FileNotFoundException {
|
||||
for (String path : paths) {
|
||||
validatePathExists(path);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates whether or not the path points at a file that exists; if the
|
||||
* path does not point to an existing file a FileNotFoundException is thrown.
|
||||
*
|
||||
* @param paths the paths to validate if they exists
|
||||
* @throws FileNoteFoundException is thrown if the path being validated does not exist.
|
||||
*/
|
||||
private void validatePathExists(String path) throws FileNotFoundException {
|
||||
File f = new File(path);
|
||||
if (!f.exists()) {
|
||||
isValid = false;
|
||||
throw new FileNotFoundException("Invalid file argument: " + path);
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Generates an Options collection that is used to parse the command line
|
||||
* and to display the help message.
|
||||
*
|
||||
* @return the command line options used for parsing the command line
|
||||
*/
|
||||
@SuppressWarnings("static-access")
|
||||
private Options createCommandLineOptions() {
|
||||
Option help = new Option(ArgumentName.HELP_SHORT, ArgumentName.HELP, false, "print this message");
|
||||
Option version = new Option(ArgumentName.VERSION_SHORT, ArgumentName.VERSION, false, "print the version information and exit");
|
||||
|
||||
Option appname = OptionBuilder.withArgName("name").hasArg().withLongOpt(ArgumentName.APPNAME).withDescription("the name of the application being scanned").create(ArgumentName.APPNAME_SHORT);
|
||||
|
||||
Option path = OptionBuilder.withArgName("path").hasArg().withLongOpt(ArgumentName.SCAN).withDescription("the path to scan").create(ArgumentName.SCAN_SHORT);
|
||||
|
||||
Option load = OptionBuilder.withArgName("file").hasArg().withLongOpt(ArgumentName.CPE).withDescription("load the CPE xml file").create(ArgumentName.CPE_SHORT);
|
||||
|
||||
Option out = OptionBuilder.withArgName("folder").hasArg().withLongOpt(ArgumentName.OUT).withDescription("the folder to write reports to.").create(ArgumentName.OUT_SHORT);
|
||||
|
||||
//TODO add the ability to load a properties file to override the defaults...
|
||||
//TODO add the ability to load the CVE entries.
|
||||
//TODO add a switch to auto-update CVE entries.
|
||||
//TODO add a switch to auto-update CPE entries.
|
||||
|
||||
OptionGroup og = new OptionGroup();
|
||||
og.addOption(path);
|
||||
og.addOption(load);
|
||||
|
||||
Options opts = new Options();
|
||||
opts.addOptionGroup(og);
|
||||
opts.addOption(out);
|
||||
opts.addOption(appname);
|
||||
opts.addOption(version);
|
||||
opts.addOption(help);
|
||||
|
||||
return opts;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines if the 'version' command line argument was passed in.
|
||||
*
|
||||
* @return whether or not the 'version' command line argument was passed in
|
||||
*/
|
||||
public boolean isGetVersion() {
|
||||
return (line != null) ? line.hasOption(ArgumentName.VERSION) : false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines if the 'help' command line argument was passed in.
|
||||
*
|
||||
* @return whether or not the 'help' command line argument was passed in
|
||||
*/
|
||||
public boolean isGetHelp() {
|
||||
return (line != null) ? line.hasOption(ArgumentName.HELP) : false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines if the 'cpe' command line argument was passed in.
|
||||
*
|
||||
* @return whether or not the 'cpe' command line argument was passed in
|
||||
*/
|
||||
public boolean isLoadCPE() {
|
||||
return (line != null) ? isValid && line.hasOption(ArgumentName.CPE) : false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines if the 'scan' command line argument was passed in.
|
||||
*
|
||||
* @return whether or not the 'scan' command line argument was passed in
|
||||
*/
|
||||
public boolean isRunScan() {
|
||||
return (line != null) ? isValid && line.hasOption(ArgumentName.SCAN) : false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Displays the command line help message to the standard output.
|
||||
*/
|
||||
public void printHelp() {
|
||||
HelpFormatter formatter = new HelpFormatter();
|
||||
formatter.printHelp("DependencyCheck", options, true);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Retrieves the file command line parameter(s) specified for the 'cpe' argument.
|
||||
*
|
||||
* @return the file paths specified on the command line
|
||||
*/
|
||||
public String getCpeFile() {
|
||||
return line.getOptionValue(ArgumentName.CPE);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the file command line parameter(s) specified for the 'scan' argument.
|
||||
*
|
||||
* @return the file paths specified on the command line for scan
|
||||
*/
|
||||
public String[] getScanFiles() {
|
||||
return line.getOptionValues(ArgumentName.SCAN);
|
||||
|
||||
}
|
||||
|
||||
public String getReportDirectory() {
|
||||
return line.getOptionValue(ArgumentName.OUT);
|
||||
}
|
||||
public String getApplicationName() {
|
||||
return line.getOptionValue(ArgumentName.APPNAME);
|
||||
}
|
||||
/**
|
||||
* <p>Prints the manifest information to standard output:</p>
|
||||
* <ul><li>Implementation-Title: ${pom.name}</li>
|
||||
* <li>Implementation-Version: ${pom.version}</li></ul>
|
||||
*/
|
||||
public void printVersionInfo() {
|
||||
String version = "DependencyCheck version unknown";
|
||||
|
||||
URLClassLoader cl = (URLClassLoader) this.getClass().getClassLoader();
|
||||
InputStream is = null;
|
||||
|
||||
try {
|
||||
URL url = cl.findResource("META-INF/MANIFEST.MF");
|
||||
is = url.openStream();
|
||||
Manifest manifest = new Manifest(is);
|
||||
Attributes atts = manifest.getMainAttributes();
|
||||
version = atts.getValue(Attributes.Name.IMPLEMENTATION_TITLE)
|
||||
+ " version "
|
||||
+ atts.getValue(Attributes.Name.IMPLEMENTATION_VERSION);
|
||||
} catch (IOException ex) {
|
||||
Logger.getLogger(CliParser.class.getName()).log(Level.WARNING, null, ex);
|
||||
} finally {
|
||||
try {
|
||||
is.close();
|
||||
is = null;
|
||||
} catch (Throwable ex) {
|
||||
Logger.getLogger(CliParser.class.getName()).log(Level.FINEST, null, ex);
|
||||
}
|
||||
}
|
||||
System.out.println(version);
|
||||
}
|
||||
|
||||
/**
|
||||
* A collection of static final strings that represent the possible command
|
||||
* line arguments.
|
||||
*/
|
||||
public static class ArgumentName {
|
||||
|
||||
public static final String SCAN = "scan";
|
||||
public static final String CPE = "cpe";
|
||||
public static final String OUT = "out";
|
||||
public static final String APPNAME = "app";
|
||||
public static final String VERSION = "version";
|
||||
public static final String HELP = "help";
|
||||
public static final String SCAN_SHORT = "s";
|
||||
public static final String CPE_SHORT = "c";
|
||||
public static final String OUT_SHORT = "o";
|
||||
public static final String VERSION_SHORT = "v";
|
||||
public static final String HELP_SHORT = "h";
|
||||
public static final String APPNAME_SHORT = "a";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,84 @@
|
||||
package org.codesecure.dependencycheck.utils;
|
||||
/*
|
||||
* This file is part of DependencyCheck.
|
||||
*
|
||||
* DependencyCheck is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* DependencyCheck is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with DependencyCheck. If not, see http://www.gnu.org/licenses/.
|
||||
*
|
||||
* Copyright (c) 2012 Jeremy Long. All Rights Reserved.
|
||||
*/
|
||||
|
||||
import java.io.BufferedOutputStream;
|
||||
import java.io.File;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.net.URL;
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
/**
|
||||
* A utility to download files from the Internet.
|
||||
*
|
||||
* @author Jeremy Long (jeremy.long@gmail.com)
|
||||
*/
|
||||
public class Downloader {
|
||||
|
||||
/**
|
||||
* Private constructor for utility class.
|
||||
*/
|
||||
private Downloader() {
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves a file from a given URL and saves it to the outputPath.
|
||||
* @param url the URL of the file to download.
|
||||
* @param outputPath the path to the save the file to.
|
||||
* @throws IOException is thrown if an IOException occurs.
|
||||
*/
|
||||
public static void fetchFile(URL url, String outputPath) throws IOException {
|
||||
File f = new File(outputPath);
|
||||
fetchFile(url, f);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves a file from a given URL and saves it to the outputPath.
|
||||
* @param url the URL of the file to download.
|
||||
* @param outputPath the path to the save the file to.
|
||||
* @throws IOException is thrown if an IOException occurs.
|
||||
*/
|
||||
public static void fetchFile(URL url, File outputPath) throws IOException {
|
||||
url.openConnection();
|
||||
BufferedOutputStream writer = null;
|
||||
try {
|
||||
InputStream reader = url.openStream();
|
||||
writer = new BufferedOutputStream(new FileOutputStream(outputPath));
|
||||
byte[] buffer = new byte[4096];
|
||||
int bytesRead = 0;
|
||||
while ((bytesRead = reader.read(buffer)) > 0) {
|
||||
writer.write(buffer, 0, bytesRead);
|
||||
}
|
||||
} catch (Exception ex) {
|
||||
Logger.getLogger(Downloader.class.getName()).log(Level.SEVERE, null, ex);
|
||||
} finally {
|
||||
try {
|
||||
writer.close();
|
||||
writer = null;
|
||||
} catch (Exception ex) {
|
||||
Logger.getLogger(Downloader.class.getName()).log(Level.WARNING,
|
||||
"Error closing the writter in Downloader.", ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
package org.codesecure.dependencycheck.utils;
|
||||
|
||||
import java.util.Iterator;
|
||||
import java.util.NoSuchElementException;
|
||||
|
||||
/*
|
||||
* This is an abstract filter that can be used to filter iterable list.
|
||||
*
|
||||
* This Filter class was copied from: http://erikras.com/2008/01/18/the-filter-pattern-java-conditional-abstraction-with-iterables/
|
||||
*
|
||||
* Erik Rasmussen - © 2006 - 2012 All Rights Reserved.
|
||||
* @author Erik Rasmussen https://plus.google.com/115403795880834599019/?rel=author
|
||||
*/
|
||||
|
||||
public abstract class Filter<T> {
|
||||
|
||||
public abstract boolean passes(T object);
|
||||
|
||||
public Iterator<T> filter(Iterator<T> iterator) {
|
||||
return new FilterIterator(iterator);
|
||||
}
|
||||
|
||||
public Iterable<T> filter(final Iterable<T> iterable) {
|
||||
return new Iterable<T>() {
|
||||
|
||||
public Iterator<T> iterator() {
|
||||
return filter(iterable.iterator());
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
private class FilterIterator implements Iterator<T> {
|
||||
|
||||
private Iterator<T> iterator;
|
||||
private T next;
|
||||
|
||||
private FilterIterator(Iterator<T> iterator) {
|
||||
this.iterator = iterator;
|
||||
toNext();
|
||||
}
|
||||
|
||||
public boolean hasNext() {
|
||||
return next != null;
|
||||
}
|
||||
|
||||
public T next() {
|
||||
if (next == null) {
|
||||
throw new NoSuchElementException();
|
||||
}
|
||||
T returnValue = next;
|
||||
toNext();
|
||||
return returnValue;
|
||||
}
|
||||
|
||||
public void remove() {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
private void toNext() {
|
||||
next = null;
|
||||
while (iterator.hasNext()) {
|
||||
T item = iterator.next();
|
||||
if (item != null && passes(item)) {
|
||||
next = item;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
677
src/main/java/org/codesecure/dependencycheck/utils/SSDeep.java
Normal file
677
src/main/java/org/codesecure/dependencycheck/utils/SSDeep.java
Normal file
@@ -0,0 +1,677 @@
|
||||
/* ssdeep
|
||||
Copyright (C) 2006 ManTech International Corporation
|
||||
|
||||
$Id: fuzzy.c 97 2010-03-19 15:10:06Z jessekornblum $
|
||||
|
||||
This program is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation; either version 2 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program; if not, write to the Free Software
|
||||
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
|
||||
The code in this file, and this file only, is based on SpamSum, part
|
||||
of the Samba project:
|
||||
http://www.samba.org/ftp/unpacked/junkcode/spamsum/
|
||||
|
||||
Because of where this file came from, any program that contains it
|
||||
must be licensed under the terms of the General Public License (GPL).
|
||||
See the file COPYING for details. The author's original comments
|
||||
about licensing are below:
|
||||
|
||||
|
||||
|
||||
this is a checksum routine that is specifically designed for spam.
|
||||
Copyright Andrew Tridgell <tridge@samba.org> 2002
|
||||
|
||||
This code is released under the GNU General Public License version 2
|
||||
or later. Alteratively, you may also use this code under the terms
|
||||
of the Perl Artistic license.
|
||||
|
||||
If you wish to distribute this code under the terms of a different
|
||||
free software license then please ask me. If there is a good reason
|
||||
then I will probably say yes.
|
||||
|
||||
*/
|
||||
|
||||
//package eu.scape_project.bitwiser.utils;
|
||||
//https://raw.github.com/openplanets/bitwiser/master/src/main/java/eu/scape_project/bitwiser/utils/SSDeep.java
|
||||
package org.codesecure.dependencycheck.utils;
|
||||
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.IOException;
|
||||
import java.util.Arrays;
|
||||
|
||||
import org.apache.commons.lang.StringUtils;
|
||||
|
||||
/**
|
||||
* SSDeep
|
||||
*
|
||||
* <p>
|
||||
* A Java version of the ssdeep algorithm, based on the fuzzy.c source
|
||||
* code, taken from version 2.6 of the ssdeep package.
|
||||
*
|
||||
* <p>
|
||||
* Transliteration/port to Java from C by...
|
||||
*
|
||||
* @author Andrew Jackson <Andrew.Jackson@bl.uk>
|
||||
*
|
||||
*/
|
||||
public class SSDeep {
|
||||
|
||||
public class FuzzyHash {
|
||||
/** the blocksize used by the program, */
|
||||
int blocksize;
|
||||
/** the hash for this blocksize */
|
||||
String hash;
|
||||
/** the hash for twice the blocksize, */
|
||||
String hash2;
|
||||
/** the filename. */
|
||||
String filename;
|
||||
}
|
||||
|
||||
/// Length of an individual fuzzy hash signature component
|
||||
public static final int SPAMSUM_LENGTH = 64;
|
||||
|
||||
/// The longest possible length for a fuzzy hash signature (without the filename)
|
||||
public static final int FUZZY_MAX_RESULT = (SPAMSUM_LENGTH + (SPAMSUM_LENGTH/2 + 20));
|
||||
|
||||
|
||||
public static final int MIN_BLOCKSIZE = 3;
|
||||
public static final int ROLLING_WINDOW = 7;
|
||||
|
||||
public static final int HASH_PRIME = 0x01000193;
|
||||
public static final int HASH_INIT = 0x28021967;
|
||||
|
||||
// Our input buffer when reading files to hash
|
||||
public static final int BUFFER_SIZE = 8192;
|
||||
|
||||
static class roll_state_class {
|
||||
int[] window = new int[ROLLING_WINDOW];
|
||||
int h1, h2, h3;
|
||||
int n;
|
||||
}
|
||||
private static roll_state_class roll_state = new roll_state_class();
|
||||
|
||||
|
||||
/*
|
||||
a rolling hash, based on the Adler checksum. By using a rolling hash
|
||||
we can perform auto resynchronisation after inserts/deletes
|
||||
|
||||
internally, h1 is the sum of the bytes in the window and h2
|
||||
is the sum of the bytes times the index
|
||||
|
||||
h3 is a shift/xor based rolling hash, and is mostly needed to ensure that
|
||||
we can cope with large blocksize values
|
||||
*/
|
||||
static int roll_hash(int c)
|
||||
{
|
||||
|
||||
// System.out.println(""+roll_state.h1+","+roll_state.h2+","+roll_state.h3);
|
||||
roll_state.h2 -= roll_state.h1;
|
||||
//roll_state.h2 = roll_state.h2 & 0x7fffffff;
|
||||
roll_state.h2 += ROLLING_WINDOW * c;
|
||||
//roll_state.h2 = roll_state.h2 & 0x7fffffff;
|
||||
|
||||
roll_state.h1 += c;
|
||||
//roll_state.h1 = roll_state.h1 & 0x7fffffff;
|
||||
roll_state.h1 -= roll_state.window[(roll_state.n % ROLLING_WINDOW)];
|
||||
//roll_state.h1 = roll_state.h1 & 0x7fffffff;
|
||||
|
||||
roll_state.window[roll_state.n % ROLLING_WINDOW] = (char)c;
|
||||
roll_state.n = (roll_state.n+1)%ROLLING_WINDOW;
|
||||
|
||||
/* The original spamsum AND'ed this value with 0xFFFFFFFF which
|
||||
in theory should have no effect. This AND has been removed
|
||||
for performance (jk) */
|
||||
roll_state.h3 = (roll_state.h3 << 5);// & 0xFFFFFFFF;
|
||||
roll_state.h3 ^= c;
|
||||
//roll_state.h3 = roll_state.h3 & 0x7FFFFFFF;
|
||||
//if( roll_state.h3 > 0xEFFFFFFF ) roll_state.h3 -= 0xEFFFFFFF;
|
||||
|
||||
long result = ((roll_state.h1 + roll_state.h2 + roll_state.h3));//&0x7FFFFFFF;
|
||||
//System.out.println("Result: "+result);
|
||||
//System.out.println("Result2: "+(result&0xFFFFFFFF));
|
||||
//System.out.println("Result3: "+(result&0x7FFFFFFF));
|
||||
|
||||
return (int) result;//&0xFFFFFFFF;
|
||||
}
|
||||
|
||||
/*
|
||||
reset the state of the rolling hash and return the initial rolling hash value
|
||||
*/
|
||||
static void roll_reset()
|
||||
{
|
||||
roll_state.h1 = 0;
|
||||
roll_state.h2 = 0;
|
||||
roll_state.h3 = 0;
|
||||
roll_state.n = 0;
|
||||
Arrays.fill(roll_state.window,(char)0);
|
||||
}
|
||||
|
||||
/* a simple non-rolling hash, based on the FNV hash */
|
||||
static int sum_hash(int c, int h)
|
||||
{
|
||||
h *= HASH_PRIME;
|
||||
//h = h & 0xFFFFFFFF;
|
||||
h ^= c;
|
||||
//h = h & 0xFFFFFFFF;
|
||||
return h;
|
||||
}
|
||||
|
||||
class ss_context {
|
||||
char[] ret;
|
||||
char[] p;
|
||||
long total_chars;
|
||||
int h, h2, h3;
|
||||
int j, n, i, k;
|
||||
int block_size;
|
||||
char[] ret2 = new char[SPAMSUM_LENGTH/2 + 1];
|
||||
}
|
||||
|
||||
|
||||
static void ss_destroy(ss_context ctx)
|
||||
{
|
||||
if (ctx.ret != null)
|
||||
ctx.ret = null;
|
||||
//free(ctx.ret);
|
||||
}
|
||||
|
||||
|
||||
static boolean ss_init(ss_context ctx, File handle)
|
||||
{
|
||||
if ( ctx == null )
|
||||
return true;
|
||||
|
||||
ctx.ret = new char[FUZZY_MAX_RESULT];
|
||||
if (ctx.ret == null)
|
||||
return true;
|
||||
|
||||
if (handle != null)
|
||||
ctx.total_chars = handle.length();
|
||||
|
||||
ctx.block_size = MIN_BLOCKSIZE;
|
||||
while (ctx.block_size * SPAMSUM_LENGTH < ctx.total_chars) {
|
||||
ctx.block_size = ctx.block_size * 2;
|
||||
}
|
||||
|
||||
System.out.println("bs:"+ctx.block_size);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
static char[] b64 = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/".toCharArray();
|
||||
|
||||
static void ss_engine(ss_context ctx,
|
||||
byte[] buffer,
|
||||
int buffer_size)
|
||||
{
|
||||
if (null == ctx || null == buffer)
|
||||
return;
|
||||
|
||||
for ( int i = 0 ; i < buffer_size ; ++i)
|
||||
{
|
||||
|
||||
/*
|
||||
at each character we update the rolling hash and
|
||||
the normal hash. When the rolling hash hits the
|
||||
reset value then we emit the normal hash as a
|
||||
element of the signature and reset both hashes
|
||||
*/
|
||||
|
||||
System.out.println(""+ctx.h+","+ctx.h2+","+ctx.h3);
|
||||
ctx.h = roll_hash(buffer[i]);// & 0x7FFFFFFF;
|
||||
ctx.h2 = sum_hash(buffer[i], ctx.h2);// & 0x7FFFFFFF;
|
||||
ctx.h3 = sum_hash(buffer[i], ctx.h3);// & 0x7FFFFFFF;
|
||||
|
||||
if (((0xFFFFFFFFl & ctx.h) % ctx.block_size) == (ctx.block_size-1)) {
|
||||
/* we have hit a reset point. We now emit a
|
||||
hash which is based on all chacaters in the
|
||||
piece of the message between the last reset
|
||||
point and this one */
|
||||
ctx.p[ctx.j] = b64[(int)((ctx.h2&0xFFFF) % 64)];
|
||||
System.out.println("::"+ctx.j+":"+new String(ctx.p));
|
||||
// for( char c : ctx.p ) {
|
||||
// System.out.print(c);
|
||||
// }
|
||||
// System.out.println();
|
||||
if (ctx.j < SPAMSUM_LENGTH-1) {
|
||||
/* we can have a problem with the tail
|
||||
overflowing. The easiest way to
|
||||
cope with this is to only reset the
|
||||
second hash if we have room for
|
||||
more characters in our
|
||||
signature. This has the effect of
|
||||
combining the last few pieces of
|
||||
the message into a single piece */
|
||||
|
||||
ctx.h2 = HASH_INIT;
|
||||
(ctx.j)++;
|
||||
}
|
||||
}
|
||||
|
||||
/* this produces a second signature with a block size
|
||||
of block_size*2. By producing dual signatures in
|
||||
this way the effect of small changes in the message
|
||||
size near a block size boundary is greatly reduced. */
|
||||
if (((0xFFFFFFFFl & ctx.h) % (ctx.block_size*2)) == ((ctx.block_size*2)-1)) {
|
||||
ctx.ret2[ctx.k] = b64[(int) (ctx.h3&0xFFFF % 64)];
|
||||
if (ctx.k < SPAMSUM_LENGTH/2-1) {
|
||||
ctx.h3 = HASH_INIT;
|
||||
(ctx.k)++;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static boolean ss_update(ss_context ctx, File handle) throws IOException
|
||||
{
|
||||
int bytes_read = 0;
|
||||
byte[] buffer;
|
||||
|
||||
if (null == ctx || null == handle)
|
||||
return true;
|
||||
|
||||
buffer = new byte[BUFFER_SIZE];
|
||||
if (buffer == null)
|
||||
return true;
|
||||
|
||||
// snprintf(ctx.ret, 12, "%u:", ctx.block_size);
|
||||
ctx.ret = (ctx.block_size + ":").toCharArray();
|
||||
// ctx.p = ctx.ret + strlen(ctx.ret);
|
||||
ctx.p = new char[SPAMSUM_LENGTH];
|
||||
|
||||
//memset(ctx.p, 0, SPAMSUM_LENGTH+1);
|
||||
Arrays.fill(ctx.p, (char)0 );
|
||||
//memset(ctx.ret2, 0, sizeof(ctx.ret2.length));
|
||||
Arrays.fill(ctx.ret2, (char)0 );
|
||||
|
||||
ctx.k = ctx.j = 0;
|
||||
ctx.h3 = ctx.h2 = HASH_INIT;
|
||||
ctx.h = 0;
|
||||
roll_reset();
|
||||
|
||||
System.out.println("Opening file:"+handle);
|
||||
FileInputStream in = new FileInputStream(handle);
|
||||
// while ((bytes_read = fread(buffer,sizeof(byte),BUFFER_SIZE,handle)) > 0)
|
||||
while (in.available() > 0 )
|
||||
{
|
||||
bytes_read = in.read(buffer);
|
||||
ss_engine(ctx,buffer,bytes_read);
|
||||
}
|
||||
|
||||
if (ctx.h != 0)
|
||||
{
|
||||
ctx.p[ctx.j] = b64[(int) ((ctx.h2 & 0xFFFF) % 64)];
|
||||
ctx.ret2[ctx.k] = b64[(int) ((ctx.h3 &0xFFFF) % 64)];
|
||||
}
|
||||
|
||||
// strcat(ctx.p+ctx.j, ":");
|
||||
// strcat(ctx.p+ctx.j, ctx.ret2);
|
||||
ctx.ret = (new String(ctx.ret) + new String(ctx.p) + ":" + new String(ctx.ret2)).toCharArray();
|
||||
|
||||
// free(buffer);
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
boolean fuzzy_hash_file(File handle) throws IOException
|
||||
{
|
||||
ss_context ctx;
|
||||
int filepos;
|
||||
boolean done = false;
|
||||
|
||||
if (null == handle)
|
||||
return true;
|
||||
|
||||
ctx = new ss_context();
|
||||
if (ctx == null)
|
||||
return true;
|
||||
|
||||
// filepos = ftello(handle);
|
||||
|
||||
ss_init(ctx, handle);
|
||||
System.out.println("bs-pre:"+ctx.block_size);
|
||||
|
||||
while (!done)
|
||||
{
|
||||
// if (fseeko(handle,0,SEEK_SET))
|
||||
// return true;
|
||||
|
||||
ss_update(ctx,handle);
|
||||
|
||||
System.out.println("RESULT:"+new String(ctx.ret));
|
||||
|
||||
// our blocksize guess may have been way off - repeat if necessary
|
||||
if (ctx.block_size > MIN_BLOCKSIZE && ctx.j < SPAMSUM_LENGTH/2)
|
||||
ctx.block_size = ctx.block_size / 2;
|
||||
else
|
||||
done = true;
|
||||
}
|
||||
|
||||
System.out.println("bs-post:"+ctx.block_size);
|
||||
// strncpy(result,ctx.ret,FUZZY_MAX_RESULT);
|
||||
|
||||
System.out.println("RESULT:"+new String(ctx.ret));
|
||||
|
||||
ss_destroy(ctx);
|
||||
// free(ctx);
|
||||
|
||||
// if (fseeko(handle,filepos,SEEK_SET))
|
||||
// return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
public boolean fuzzy_hash_filename(String filename) throws IOException
|
||||
{
|
||||
boolean status;
|
||||
|
||||
if (null == filename)
|
||||
return true;
|
||||
|
||||
File handle = new File(filename);//,"rb");
|
||||
if (null == handle)
|
||||
return true;
|
||||
|
||||
status = fuzzy_hash_file(handle);
|
||||
|
||||
// fclose(handle);
|
||||
|
||||
return status;
|
||||
}
|
||||
|
||||
|
||||
boolean fuzzy_hash_buf(byte[] buf,
|
||||
int buf_len,
|
||||
char[] result)
|
||||
{
|
||||
ss_context ctx = new ss_context();
|
||||
boolean done = false;
|
||||
|
||||
if (buf == null)
|
||||
return true;
|
||||
|
||||
ctx.total_chars = buf_len;
|
||||
ss_init(ctx, null);
|
||||
|
||||
System.out.println("total_chars: "+ctx.total_chars);
|
||||
|
||||
while (!done)
|
||||
{
|
||||
// snprintf(ctx.ret, 12, "%u:", ctx.block_size);
|
||||
// ctx.p = ctx.ret + strlen(ctx.ret);
|
||||
ctx.p = new char[SPAMSUM_LENGTH+1]; // TODO Duplication!
|
||||
|
||||
// memset(ctx.p, 0, SPAMSUM_LENGTH+1);
|
||||
// memset(ctx.ret2, 0, sizeof(ctx.ret2));
|
||||
|
||||
ctx.k = ctx.j = 0;
|
||||
ctx.h3 = ctx.h2 = HASH_INIT;
|
||||
ctx.h = 0;
|
||||
roll_reset();
|
||||
|
||||
System.out.println("h:"+ctx.h);
|
||||
System.out.println("h2:"+ctx.h2);
|
||||
|
||||
ss_engine(ctx,buf,buf_len);
|
||||
|
||||
/* our blocksize guess may have been way off - repeat if necessary */
|
||||
if (ctx.block_size > MIN_BLOCKSIZE && ctx.j < SPAMSUM_LENGTH/2)
|
||||
ctx.block_size = ctx.block_size / 2;
|
||||
else
|
||||
done = true;
|
||||
|
||||
System.out.println("h:"+ctx.h);
|
||||
System.out.println("h2:"+ctx.h2);
|
||||
System.out.println("h3:"+ctx.h3);
|
||||
System.out.println("bs:"+ctx.block_size);
|
||||
System.out.println("ret:"+new String(ctx.ret));
|
||||
System.out.println("p:"+new String(ctx.p));
|
||||
System.out.println("ret2:"+new String(ctx.ret2));
|
||||
if (ctx.h != 0)
|
||||
{
|
||||
ctx.p[ctx.j] = b64[(int) ((ctx.h2&0xFFFF) % 64)];
|
||||
ctx.ret2[ctx.k] = b64[(int) ((ctx.h3&0xFFFF) % 64)];
|
||||
}
|
||||
|
||||
// strcat(ctx.p+ctx.j, ":");
|
||||
// strcat(ctx.p+ctx.j, ctx.ret2);
|
||||
}
|
||||
|
||||
|
||||
// strncpy(result,ctx.ret,FUZZY_MAX_RESULT);
|
||||
System.out.println("bs:"+ctx.block_size);
|
||||
System.out.println("ret:"+new String(ctx.ret));
|
||||
System.out.println("p:"+new String(ctx.p));
|
||||
System.out.println("ret2:"+new String(ctx.ret2));
|
||||
System.out.println("h3:"+ctx.h3);
|
||||
result = ctx.ret;
|
||||
|
||||
ss_destroy(ctx);
|
||||
// free(ctx);
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
/*
|
||||
we only accept a match if we have at least one common substring in
|
||||
the signature of length ROLLING_WINDOW. This dramatically drops the
|
||||
false positive rate for low score thresholds while having
|
||||
negligable affect on the rate of spam detection.
|
||||
|
||||
return 1 if the two strings do have a common substring, 0 otherwise
|
||||
*/
|
||||
static int has_common_substring(char[] s1, char[] s2)
|
||||
{
|
||||
int i, j;
|
||||
int num_hashes;
|
||||
long[] hashes = new long[SPAMSUM_LENGTH];
|
||||
|
||||
/* there are many possible algorithms for common substring
|
||||
detection. In this case I am re-using the rolling hash code
|
||||
to act as a filter for possible substring matches */
|
||||
|
||||
roll_reset();
|
||||
// memset(hashes, 0, sizeof(hashes));
|
||||
|
||||
/* first compute the windowed rolling hash at each offset in
|
||||
the first string */
|
||||
for (i=0;s1[i] != 0;i++)
|
||||
{
|
||||
hashes[i] = roll_hash((char)s1[i]);
|
||||
}
|
||||
num_hashes = i;
|
||||
|
||||
roll_reset();
|
||||
|
||||
/* now for each offset in the second string compute the
|
||||
rolling hash and compare it to all of the rolling hashes
|
||||
for the first string. If one matches then we have a
|
||||
candidate substring match. We then confirm that match with
|
||||
a direct string comparison */
|
||||
for (i=0;s2[i] != 0;i++) {
|
||||
long h = roll_hash((char)s2[i]);
|
||||
if (i < ROLLING_WINDOW-1) continue;
|
||||
for (j=ROLLING_WINDOW-1;j<num_hashes;j++)
|
||||
{
|
||||
if (hashes[j] != 0 && hashes[j] == h)
|
||||
{
|
||||
/* we have a potential match - confirm it */
|
||||
/*FIXME
|
||||
if (strlen(s2+i-(ROLLING_WINDOW-1)) >= ROLLING_WINDOW &&
|
||||
strncmp(s2+i-(ROLLING_WINDOW-1),
|
||||
s1+j-(ROLLING_WINDOW-1),
|
||||
ROLLING_WINDOW) == 0)
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
*/
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
// eliminate sequences of longer than 3 identical characters. These
|
||||
// sequences contain very little information so they tend to just bias
|
||||
// the result unfairly
|
||||
static char[] eliminate_sequences(String string)
|
||||
{
|
||||
char[] str = string.toCharArray();
|
||||
StringBuffer ret = new StringBuffer();
|
||||
|
||||
// Do not include repeats:
|
||||
for (int i=3;i<str.length;i++) {
|
||||
if (str[i] != str[i-1] ||
|
||||
str[i] != str[i-2] ||
|
||||
str[i] != str[i-3]) {
|
||||
ret.append(str[i]);
|
||||
}
|
||||
}
|
||||
|
||||
return ret.toString().toCharArray();
|
||||
}
|
||||
|
||||
/*
|
||||
this is the low level string scoring algorithm. It takes two strings
|
||||
and scores them on a scale of 0-100 where 0 is a terrible match and
|
||||
100 is a great match. The block_size is used to cope with very small
|
||||
messages.
|
||||
*/
|
||||
static int score_strings(char[] s1, char[] s2, int block_size)
|
||||
{
|
||||
int score = 0;
|
||||
int len1, len2;
|
||||
|
||||
len1 = s1.length;
|
||||
len2 = s2.length;
|
||||
|
||||
if (len1 > SPAMSUM_LENGTH || len2 > SPAMSUM_LENGTH) {
|
||||
/* not a real spamsum signature? */
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* the two strings must have a common substring of length
|
||||
ROLLING_WINDOW to be candidates */
|
||||
if (has_common_substring(s1, s2) == 0) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* compute the edit distance between the two strings. The edit distance gives
|
||||
us a pretty good idea of how closely related the two strings are */
|
||||
score = StringUtils.getLevenshteinDistance(new String(s1), new String(s2));
|
||||
|
||||
/* scale the edit distance by the lengths of the two
|
||||
strings. This changes the score to be a measure of the
|
||||
proportion of the message that has changed rather than an
|
||||
absolute quantity. It also copes with the variability of
|
||||
the string lengths. */
|
||||
score = (score * SPAMSUM_LENGTH) / (len1 + len2);
|
||||
|
||||
/* at this stage the score occurs roughly on a 0-64 scale,
|
||||
* with 0 being a good match and 64 being a complete
|
||||
* mismatch */
|
||||
|
||||
/* rescale to a 0-100 scale (friendlier to humans) */
|
||||
score = (100 * score) / 64;
|
||||
|
||||
/* it is possible to get a score above 100 here, but it is a
|
||||
really terrible match */
|
||||
if (score >= 100) return 0;
|
||||
|
||||
/* now re-scale on a 0-100 scale with 0 being a poor match and
|
||||
100 being a excellent match. */
|
||||
score = 100 - score;
|
||||
|
||||
// printf ("len1: %"PRIu32" len2: %"PRIu32"\n", len1, len2);
|
||||
|
||||
/* when the blocksize is small we don't want to exaggerate the match size */
|
||||
if (score > block_size/MIN_BLOCKSIZE * Math.min(len1, len2)) {
|
||||
score = block_size/MIN_BLOCKSIZE * Math.min(len1, len2);
|
||||
}
|
||||
return score;
|
||||
}
|
||||
|
||||
/*
|
||||
given two spamsum strings return a value indicating the degree to which they match.
|
||||
*/
|
||||
int fuzzy_compare(FuzzyHash fh1, FuzzyHash fh2 )
|
||||
{
|
||||
int score = 0;
|
||||
char[] s1_1, s1_2;
|
||||
char[] s2_1, s2_2;
|
||||
|
||||
// if the blocksizes don't match then we are comparing
|
||||
// apples to oranges. This isn't an 'error' per se. We could
|
||||
// have two valid signatures, but they can't be compared.
|
||||
if (fh1.blocksize != fh2.blocksize &&
|
||||
fh1.blocksize != fh2.blocksize*2 &&
|
||||
fh2.blocksize != fh1.blocksize*2) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
// there is very little information content is sequences of
|
||||
// the same character like 'LLLLL'. Eliminate any sequences
|
||||
// longer than 3. This is especially important when combined
|
||||
// with the has_common_substring() test below.
|
||||
s1_1 = eliminate_sequences(fh1.hash+1);
|
||||
s2_1 = eliminate_sequences(fh2.hash+1);
|
||||
|
||||
s1_2 = eliminate_sequences(fh1.hash2+1);
|
||||
s2_2 = eliminate_sequences(fh1.hash2+1);
|
||||
|
||||
// each signature has a string for two block sizes. We now
|
||||
// choose how to combine the two block sizes. We checked above
|
||||
// that they have at least one block size in common
|
||||
if (fh1.blocksize == fh2.blocksize) {
|
||||
int score1, score2;
|
||||
score1 = score_strings(s1_1, s2_1, fh1.blocksize);
|
||||
score2 = score_strings(s1_2, s2_2, fh2.blocksize);
|
||||
|
||||
// s.block_size = fh1.blocksize;
|
||||
|
||||
score = Math.max(score1, score2);
|
||||
} else if (fh1.blocksize == fh2.blocksize*2) {
|
||||
|
||||
score = score_strings(s1_1, s2_2, fh1.blocksize);
|
||||
// s.block_size = fh1.blocksize;
|
||||
} else {
|
||||
|
||||
score = score_strings(s1_2, s2_1, fh2.blocksize);
|
||||
// s.block_size = fh2.blocksize;
|
||||
}
|
||||
|
||||
return (int)score;
|
||||
}
|
||||
|
||||
/**
|
||||
* Main class for quick testing.
|
||||
* @param args
|
||||
* @throws IOException
|
||||
*/
|
||||
public static void main( String[] args ) throws IOException {
|
||||
SSDeep ssd = new SSDeep();
|
||||
byte[] b2 = "Hello World how are you today...\n".getBytes();
|
||||
byte[] b3 = "Helli".getBytes();
|
||||
char[] h1 = null;
|
||||
boolean t1 = ssd.fuzzy_hash_buf(b2, b2.length, h1);
|
||||
System.out.println("Got "+h1);
|
||||
ssd.fuzzy_hash_file(new File("test"));
|
||||
//ssd.fuzzy_hash_file(new File("pom.xml"));
|
||||
}
|
||||
}
|
||||
139
src/main/java/org/codesecure/dependencycheck/utils/Settings.java
Normal file
139
src/main/java/org/codesecure/dependencycheck/utils/Settings.java
Normal file
@@ -0,0 +1,139 @@
|
||||
package org.codesecure.dependencycheck.utils;
|
||||
/*
|
||||
* This file is part of DependencyCheck.
|
||||
*
|
||||
* DependencyCheck is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* DependencyCheck is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with DependencyCheck. If not, see http://www.gnu.org/licenses/.
|
||||
*
|
||||
* Copyright (c) 2012 Jeremy Long. All Rights Reserved.
|
||||
*/
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.util.Enumeration;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Properties;
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
/**
|
||||
* A simple settings container that wraps the dependencycheck.properties file.
|
||||
*
|
||||
* @author Jeremy Long (jeremy.long@gmail.com)
|
||||
*/
|
||||
public class Settings {
|
||||
|
||||
/**
|
||||
* The collection of keys used within the properties file.
|
||||
*/
|
||||
public abstract class KEYS {
|
||||
|
||||
/**
|
||||
* The properties key for the path where the CPE Lucene Index will be stored.
|
||||
*/
|
||||
public static final String CPE_INDEX = "index.cpe";
|
||||
/**
|
||||
* The properties key for the URL to the CPE.
|
||||
*/
|
||||
public static final String CPE_URL = "index.cpe.url";
|
||||
/**
|
||||
* The properties key for the path where the CCE Lucene Index will be stored.
|
||||
*/
|
||||
public static final String CVE_INDEX = "index.cve";
|
||||
/**
|
||||
* The properties key for the path where the OSVDB Lucene Index will be stored.
|
||||
*/
|
||||
public static final String OSVDB_INDEX = "index.osvdb";
|
||||
/**
|
||||
* The properties key prefix for the analyzer assocations.
|
||||
*/
|
||||
public static final String FILE_EXTENSION_ANALYZER_ASSOCIATION_PREFIX = "file.extension.analyzer.association.";
|
||||
}
|
||||
private static final String PROPERTIES_FILE = "dependencycheck.properties";
|
||||
private static Settings instance = new Settings();
|
||||
private Properties props = null;
|
||||
|
||||
/**
|
||||
* Private contructor for the Settings class. This class loads the properties files.
|
||||
*/
|
||||
private Settings() {
|
||||
InputStream in = this.getClass().getClassLoader().getResourceAsStream(PROPERTIES_FILE);
|
||||
props = new Properties();
|
||||
try {
|
||||
props.load(in);
|
||||
} catch (IOException ex) {
|
||||
Logger.getLogger(Settings.class.getName()).log(Level.SEVERE, null, ex);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a value from the properties file. If the value was specified as a
|
||||
* system property or passed in via the -Dprop=value argument - this method
|
||||
* will return the value from the system properties before the values in
|
||||
* the contained configuration file.
|
||||
*
|
||||
* @param key the key to lookup within the properties file.
|
||||
* @return the property from the properties file.
|
||||
*/
|
||||
public static String getString(String key) {
|
||||
return System.getProperty(key, instance.props.getProperty(key));
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a map of properties selected by a given prefix. For isntance
|
||||
* if you have five properties that started off with "org.codesecure.name"
|
||||
* you could get a collection of those properties by calling this method.
|
||||
*
|
||||
* NOTE: The prefix is removed from the given properties when returned.
|
||||
*
|
||||
* @param prefix the prefix used to search the property collections for.
|
||||
* @return a Map of properties found.
|
||||
*/
|
||||
public static Map<String, String> getPropertiesByPrefix(String prefix) {
|
||||
Map<String, String> ret = new HashMap<String, String>();
|
||||
|
||||
Properties properties = instance.props;
|
||||
for (Enumeration<Object> e = properties.keys(); e.hasMoreElements(); ) {
|
||||
Object o = e.nextElement();
|
||||
if (o instanceof String) {
|
||||
String key = (String) o;
|
||||
if (key.startsWith(prefix)) {
|
||||
String ext = key.substring(prefix.length());
|
||||
ret.put(ext, properties.getProperty(key));
|
||||
}
|
||||
}
|
||||
}
|
||||
properties = System.getProperties();
|
||||
for (Enumeration<Object> e = properties.keys(); e.hasMoreElements(); ) {
|
||||
Object o = e.nextElement();
|
||||
if (o instanceof String) {
|
||||
String key = (String) o;
|
||||
if (key.startsWith(prefix)) {
|
||||
String ext = key.substring(prefix.length() + 1);
|
||||
ret.put(ext, properties.getProperty(key));
|
||||
}
|
||||
}
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
// public static boolean getBoolean(String key) {
|
||||
// return Boolean.parseBoolean(instance.props.getProperty(key));
|
||||
// }
|
||||
// public static long getLong(String key) {
|
||||
// return Long.parseLong(instance.props.getProperty(key));
|
||||
// }
|
||||
// public static int getInt(String key) {
|
||||
// return Integer.parseInt(instance.props.getProperty(key));
|
||||
// }
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
/**
|
||||
* <html>
|
||||
* <head>
|
||||
* <title>org.codesecure.dependencycheck.utils</title>
|
||||
* </head>
|
||||
* <body>
|
||||
* Includes various utility classes such as a Settings wrapper, a CLI Parser,
|
||||
* a Checksum utility, etc.
|
||||
* </body>
|
||||
* </html>
|
||||
*/
|
||||
|
||||
package org.codesecure.dependencycheck.utils;
|
||||
Reference in New Issue
Block a user