[#33] IMA baselines can match measurements based solely on hashes (#34)

ImaAcceptableRecordBaseline and its subclasses have been updated to include
a containsHashes method to be able to match IMA measurement records
based solely on their hashes.  Supporting classes have been
updated or created as necessary.

Additionally, the set of path equivalencies as specified in the IMA
policy have been updated to include additional entries.

Closes #33.
This commit is contained in:
apldev1 2018-11-01 10:47:33 -04:00 committed by apldev4
parent 37ba6de3cd
commit 2d0806e5a8
17 changed files with 769 additions and 95 deletions

View File

@ -457,9 +457,11 @@ public class IMAPolicy extends Policy implements HasBaselines {
Multimap<String, String> equivalentPaths = HashMultimap.create();
// define equivalences
equivalentPaths.put("/bin/", "/usr/bin/");
equivalentPaths.put("/lib/", "/usr/lib/");
equivalentPaths.put("/lib64/", "/usr/lib64/");
equivalentPaths.put("/bin/", "/usr/bin/");
equivalentPaths.put("/lib/", "/usr/lib/");
equivalentPaths.put("/lib64/", "/usr/lib64/");
equivalentPaths.put("/usr/bin/", "/usr/sbin/");
equivalentPaths.put("/sbin/", "/usr/sbin/");
// populate inverse relationships
Multimap<String, String> bidirectionalEquivalences = HashMultimap.create();

View File

@ -1,6 +1,7 @@
package hirs.data.persist;
import com.fasterxml.jackson.annotation.JsonIgnore;
import hirs.ima.matching.BatchImaMatchStatus;
import hirs.persist.ImaBaselineRecordManager;
import javax.persistence.Entity;
@ -14,6 +15,7 @@ import java.util.Set;
*/
@Entity
public abstract class ImaAcceptableRecordBaseline extends ImaBaseline<IMABaselineRecord> {
/**
* Creates a new ImaAcceptableRecordBaseline with the given name.
*
@ -29,6 +31,34 @@ public abstract class ImaAcceptableRecordBaseline extends ImaBaseline<IMABaselin
protected ImaAcceptableRecordBaseline() {
}
/**
* Similar to contains, but only considers the hash value and does not consider
* the path as relevant to matching at all.
*
* Each type of baseline specifies its own
* 'contains' algorithm for deciding whether the given measurements are
* considered as matches, mismatches, or unknowns to the baseline. The 'contains' method
* of ImaAcceptableRecordBaselines that is normally used to judge measurement records
* against baseline records considers both paths and hashes; this method offers an
* additional mechanism for finding matching baseline records solely based
* on matching hash values.
*
* @param records
* measurement records to find in this baseline
* @param recordManager
* an ImaBaselineRecordManager that can be used to retrieve persisted records
* @param imaPolicy
* the IMA policy to use while determining if a baseline contains the given records
*
* @return batch match status for the measurement records, according only to hashes
*/
@JsonIgnore
public abstract BatchImaMatchStatus<IMABaselineRecord> containsHashes(
Collection<IMAMeasurementRecord> records,
ImaBaselineRecordManager recordManager,
IMAPolicy imaPolicy
);
/**
* Returns an unmodifiable set of IMA baseline records found in the IMA
* baseline. The returned set only contains the baseline records from this

View File

@ -75,7 +75,7 @@ public abstract class ImaBaseline<T extends AbstractImaBaselineRecord> extends B
* @param imaPolicy
* the IMA policy to use while determining if a baseline contains the given records
*
* @return search status for the measurement record
* @return batch match status for the measurement records
*/
public abstract BatchImaMatchStatus<T> contains(
Collection<IMAMeasurementRecord> records,

View File

@ -3,7 +3,6 @@ package hirs.data.persist;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.google.common.base.Preconditions;
import hirs.ima.matching.BatchImaMatchStatus;
import hirs.ima.matching.IMAMatchStatus;
import hirs.ima.matching.ImaBlacklistRecordMatcher;
import hirs.persist.ImaBaselineRecordManager;
@ -11,11 +10,9 @@ import javax.persistence.CascadeType;
import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.OneToMany;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;
/**
@ -53,22 +50,10 @@ public class ImaBlacklistBaseline extends ImaBaseline<ImaBlacklistRecord> {
final Collection<IMAMeasurementRecord> records,
final ImaBaselineRecordManager recordManager,
final IMAPolicy imaPolicy) {
if (records == null) {
throw new IllegalArgumentException("Records cannot be null");
}
if (imaPolicy == null) {
throw new IllegalArgumentException("IMA policy cannot be null");
}
ImaBlacklistRecordMatcher recordMatcher =
new ImaBlacklistRecordMatcher(imaBlacklistRecords, imaPolicy, this);
List<IMAMatchStatus<ImaBlacklistRecord>> matchStatuses = new ArrayList<>();
for (IMAMeasurementRecord record : records) {
matchStatuses.add(recordMatcher.contains(record));
}
return new BatchImaMatchStatus<>(matchStatuses);
Preconditions.checkArgument(records != null, "Records cannot be null");
Preconditions.checkArgument(imaPolicy != null, "IMA policy cannot be null");
return new ImaBlacklistRecordMatcher(imaBlacklistRecords, imaPolicy, this)
.batchMatch(records);
}
/**

View File

@ -7,17 +7,15 @@ package hirs.data.persist;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.google.common.base.Preconditions;
import hirs.ima.matching.BatchImaMatchStatus;
import hirs.ima.matching.IMAMatchStatus;
import hirs.ima.matching.ImaIgnoreSetRecordMatcher;
import hirs.persist.ImaBaselineRecordManager;
import hirs.utils.RegexFilePathMatcher;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import javax.persistence.Access;
@ -157,21 +155,10 @@ public class ImaIgnoreSetBaseline extends ImaBaseline<ImaIgnoreSetRecord> {
final ImaBaselineRecordManager recordManager,
final IMAPolicy imaPolicy
) {
if (records == null) {
throw new IllegalArgumentException("Records cannot be null");
}
if (imaPolicy == null) {
throw new IllegalArgumentException("IMA policy cannot be null");
}
ImaIgnoreSetRecordMatcher recordMatcher =
new ImaIgnoreSetRecordMatcher(imaIgnoreSetRecords, imaPolicy, this);
List<IMAMatchStatus<ImaIgnoreSetRecord>> matchStatuses = new ArrayList<>();
for (IMAMeasurementRecord record : records) {
matchStatuses.add(recordMatcher.contains(record));
}
return new BatchImaMatchStatus<>(matchStatuses);
Preconditions.checkArgument(records != null, "Records cannot be null");
Preconditions.checkArgument(imaPolicy != null, "IMA policy cannot be null");
return new ImaIgnoreSetRecordMatcher(imaIgnoreSetRecords, imaPolicy, this)
.batchMatch(records);
}
/**

View File

@ -1,18 +1,20 @@
package hirs.data.persist;
import com.google.common.base.Preconditions;
import hirs.ima.matching.BatchImaMatchStatus;
import hirs.ima.matching.IMAMatchStatus;
import hirs.ima.matching.ImaAcceptableRecordMatcher;
import hirs.ima.matching.ImaAcceptableHashRecordMatcher;
import hirs.ima.matching.ImaAcceptablePathAndHashRecordMatcher;
import hirs.ima.matching.ImaRecordMatcher;
import hirs.persist.ImaBaselineRecordManager;
import hirs.utils.Callback;
import org.hibernate.Criteria;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
import java.util.List;
import java.util.Objects;
import java.util.Set;
import java.util.stream.Collectors;
/**
* This class defines the basis of operation for a baseline that supports querying
@ -50,13 +52,8 @@ public abstract class QueryableRecordImaBaseline extends ImaAcceptableRecordBase
final Collection<IMAMeasurementRecord> records,
final ImaBaselineRecordManager recordManager,
final IMAPolicy imaPolicy) {
if (records == null) {
throw new IllegalArgumentException("Records cannot be null");
}
if (recordManager == null) {
throw new IllegalArgumentException("ImaBaselineRecordManager cannot be null");
}
Preconditions.checkArgument(records != null, "records cannot be null");
Preconditions.checkArgument(recordManager != null, "record manager cannot be null");
final Collection<String> pathsToFind = new HashSet<>();
for (IMAMeasurementRecord record : records) {
@ -79,14 +76,45 @@ public abstract class QueryableRecordImaBaseline extends ImaAcceptableRecordBase
}
});
ImaAcceptableRecordMatcher recordMatcher =
new ImaAcceptableRecordMatcher(retrievedRecords, imaPolicy, this);
List<IMAMatchStatus<IMABaselineRecord>> matchStatuses = new ArrayList<>();
for (IMAMeasurementRecord record : records) {
matchStatuses.add(recordMatcher.contains(record));
}
return new ImaAcceptablePathAndHashRecordMatcher(retrievedRecords, imaPolicy, this)
.batchMatch(records);
}
return new BatchImaMatchStatus<>(matchStatuses);
/**
* Check membership of the given {@link IMAMeasurementRecord}s in this baseline.
*
* @param records the records to attempt to match
* @param recordManager the {@link ImaBaselineRecordManager} to query
* @param imaPolicy the IMA policy to use while determining if a baseline contains the records
*
* @return a collection of {@link IMAMatchStatus}es reflecting the results
*/
@Override
public final BatchImaMatchStatus<IMABaselineRecord> containsHashes(
final Collection<IMAMeasurementRecord> records,
final ImaBaselineRecordManager recordManager,
final IMAPolicy imaPolicy) {
Preconditions.checkArgument(records != null, "records cannot be null");
Preconditions.checkArgument(recordManager != null, "record manager cannot be null");
final Set<Digest> hashesToFind = records.stream()
.filter(Objects::nonNull)
.map(IMAMeasurementRecord::getHash)
.collect(Collectors.toSet());
Collection<IMABaselineRecord> retrievedRecords = recordManager.iterateOverBaselineRecords(
this, new Callback<IMABaselineRecord, IMABaselineRecord>() {
@Override
public IMABaselineRecord call(final IMABaselineRecord baselineRecord) {
if (hashesToFind.contains(baselineRecord.getHash())) {
return baselineRecord;
}
return null;
}
});
return new ImaAcceptableHashRecordMatcher(retrievedRecords, imaPolicy, this)
.batchMatch(records);
}
@Override

View File

@ -1,9 +1,10 @@
package hirs.data.persist;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.google.common.base.Preconditions;
import hirs.ima.matching.BatchImaMatchStatus;
import hirs.ima.matching.IMAMatchStatus;
import hirs.ima.matching.ImaAcceptableRecordMatcher;
import hirs.ima.matching.ImaAcceptableHashRecordMatcher;
import hirs.ima.matching.ImaAcceptablePathAndHashRecordMatcher;
import hirs.persist.ImaBaselineRecordManager;
import org.apache.logging.log4j.Logger;
@ -15,12 +16,10 @@ import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.OneToMany;
import java.net.URL;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;
import static org.apache.logging.log4j.LogManager.getLogger;
@ -175,21 +174,24 @@ public class SimpleImaBaseline extends ImaAcceptableRecordBaseline {
final Collection<IMAMeasurementRecord> records,
final ImaBaselineRecordManager recordManager,
final IMAPolicy imaPolicy) {
if (records == null) {
throw new IllegalArgumentException("Records cannot be null");
}
Preconditions.checkArgument(records != null, "Records cannot be null");
Preconditions.checkArgument(imaPolicy != null, "IMA policy cannot be null");
if (imaPolicy == null) {
throw new IllegalArgumentException("IMA policy cannot be null");
}
return new ImaAcceptablePathAndHashRecordMatcher(imaRecords, imaPolicy, this)
.batchMatch(records);
}
ImaAcceptableRecordMatcher recordMatcher =
new ImaAcceptableRecordMatcher(imaRecords, imaPolicy, this);
List<IMAMatchStatus<IMABaselineRecord>> matchStatuses = new ArrayList<>();
for (IMAMeasurementRecord record : records) {
matchStatuses.add(recordMatcher.contains(record));
}
return new BatchImaMatchStatus<>(matchStatuses);
@Override
public BatchImaMatchStatus<IMABaselineRecord> containsHashes(
final Collection<IMAMeasurementRecord> records,
final ImaBaselineRecordManager recordManager,
final IMAPolicy imaPolicy) {
Preconditions.checkArgument(records != null, "Records cannot be null");
Preconditions.checkArgument(imaPolicy != null, "IMA policy cannot be null");
return new ImaAcceptableHashRecordMatcher(imaRecords, imaPolicy, this)
.batchMatch(records);
}
@Override

View File

@ -307,4 +307,11 @@ public class BatchImaMatchStatus<T extends AbstractImaBaselineRecord> {
public int hashCode() {
return Objects.hash(matchStatuses);
}
@Override
public String toString() {
return "BatchImaMatchStatus{"
+ "matchStatuses=" + matchStatuses
+ '}';
}
}

View File

@ -0,0 +1,59 @@
package hirs.ima.matching;
import com.google.common.base.Preconditions;
import hirs.data.persist.IMABaselineRecord;
import hirs.data.persist.IMAMeasurementRecord;
import hirs.data.persist.IMAPolicy;
import hirs.data.persist.ImaBaseline;
import hirs.data.persist.ReportMatchStatus;
import java.util.Collection;
import java.util.Set;
/**
* This class extends the base matching functionality of {@link ImaRecordMatcher} to
* compare {@link IMAMeasurementRecord}s against a collection of {@link IMABaselineRecord}s
* based solely on their hashes.
*/
public class ImaAcceptableHashRecordMatcher extends ImaRecordMatcher<IMABaselineRecord> {
/**
* Construct a new ImaAcceptablePathAndHashRecordMatcher.
*
* @param records the baseline records to use for matching
* @param imaPolicy the IMA policy to reference during matching; its partial path and path
* equivalence settings influence matching behavior
* @param imaBaseline the IMA baseline these records were sourced from; this is only used to
*/
public ImaAcceptableHashRecordMatcher(
final Collection<IMABaselineRecord> records,
final IMAPolicy imaPolicy,
final ImaBaseline imaBaseline) {
super(records, imaPolicy, imaBaseline);
}
/**
* Returns an IMAMatchStatus indicating whether the given {@link IMAMeasurementRecord} is
* contained within the originally provided {@link IMABaselineRecord}s.
*
* @param record the record to look up
* @return an IMAMatchStatus indicating whether the record is a match or unknown to
* the given baseline records
*/
@Override
public IMAMatchStatus<IMABaselineRecord> contains(final IMAMeasurementRecord record) {
Preconditions.checkArgument(record != null, "Cannot match on null record.");
final Set<IMABaselineRecord> matchingRecords = getRelatedBaselineRecordsByHash(record);
if (matchingRecords.isEmpty()) {
return new IMAMatchStatus<>(record, ReportMatchStatus.UNKNOWN, getImaBaseline());
}
return new IMAMatchStatus<>(
record,
ReportMatchStatus.MATCH,
matchingRecords,
getImaBaseline()
);
}
}

View File

@ -1,5 +1,6 @@
package hirs.ima.matching;
import com.google.common.base.Preconditions;
import hirs.data.persist.DigestComparisonResultType;
import hirs.data.persist.IMABaselineRecord;
import hirs.data.persist.IMAMeasurementRecord;
@ -16,20 +17,21 @@ import static org.apache.logging.log4j.LogManager.getLogger;
/**
* This class extends the base matching functionality of {@link ImaRecordMatcher} to
* compare {@link IMAMeasurementRecord}s against a collection of {@link IMABaselineRecord}s.
* compare {@link IMAMeasurementRecord}s against a collection of {@link IMABaselineRecord}s
* based on both their paths and hashes.
*/
public class ImaAcceptableRecordMatcher extends ImaRecordMatcher<IMABaselineRecord> {
private static final Logger LOGGER = getLogger(ImaAcceptableRecordMatcher.class);
public class ImaAcceptablePathAndHashRecordMatcher extends ImaRecordMatcher<IMABaselineRecord> {
private static final Logger LOGGER = getLogger(ImaAcceptablePathAndHashRecordMatcher.class);
/**
* Construct a new ImaAcceptableRecordMatcher.
* Construct a new ImaAcceptablePathAndHashRecordMatcher.
*
* @param records the baseline records to use for matching
* @param imaPolicy the IMA policy to reference during matching; its partial path and path
* equivalence settings influence matching behavior
* @param imaBaseline the IMA baseline these records were sourced from; this is only used to
*/
public ImaAcceptableRecordMatcher(
public ImaAcceptablePathAndHashRecordMatcher(
final Collection<IMABaselineRecord> records,
final IMAPolicy imaPolicy,
final ImaBaseline imaBaseline) {
@ -46,9 +48,7 @@ public class ImaAcceptableRecordMatcher extends ImaRecordMatcher<IMABaselineReco
*/
@Override
public IMAMatchStatus<IMABaselineRecord> contains(final IMAMeasurementRecord record) {
if (record == null) {
throw new IllegalArgumentException("Cannot match on null record.");
}
Preconditions.checkArgument(record != null, "Cannot match on null record.");
final Set<IMABaselineRecord> matchRecords = new HashSet<>();
final Set<IMABaselineRecord> mismatchRecords = new HashSet<>();

View File

@ -9,8 +9,10 @@ import hirs.data.persist.IMAPolicy;
import hirs.data.persist.AbstractImaBaselineRecord;
import hirs.data.persist.ImaBaseline;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
/**
@ -67,6 +69,22 @@ public abstract class ImaRecordMatcher<T extends AbstractImaBaselineRecord> {
*/
public abstract IMAMatchStatus<T> contains(IMAMeasurementRecord record);
/**
* Given a collection of measurement records, populate and return a BatchImaMatchStatus
* instance containing the match results according to this ImaRecordMatcher's matching
* behavior and the given IMA policy, baseline, and baseline records.
*
* @param records the measurement records to match to baseline records
* @return a BatchImaMatchStatus containing the match status of all the given records
*/
public BatchImaMatchStatus<T> batchMatch(final Collection<IMAMeasurementRecord> records) {
List<IMAMatchStatus<T>> matchStatuses = new ArrayList<>();
for (IMAMeasurementRecord record : records) {
matchStatuses.add(contains(record));
}
return new BatchImaMatchStatus<>(matchStatuses);
}
/**
* Gets all IMA baseline records that are related to the given IMA measurement record
* as determined by path similarity or equivalency. This method respects the IMA policy

View File

@ -5,6 +5,7 @@ import java.util.Collections;
import java.util.HashSet;
import java.util.Set;
import hirs.ima.matching.BatchImaMatchStatus;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import hirs.ima.matching.IMAMatchStatus;
@ -249,6 +250,122 @@ public class BroadRepoImaBaselineTest extends SpringPersistenceTest {
);
}
/**
* Test that ensures a BroadRepoImaBaseline can correctly determine if
* it contains any matching baseline records solely based upon a given measurement
* record's hash.
*
* @throws UnsupportedEncodingException
* if an error is encountered while getting the test digest
*/
@Test
public final void containsHashes() throws UnsupportedEncodingException {
BroadRepoImaBaseline testBaseline = new BroadRepoImaBaseline(BASELINE_NAME);
Repository testRepo = new TestRepository("Test Repository", 0);
DBRepositoryManager repoManager = new DBRepositoryManager(sessionFactory);
testRepo = repoManager.saveRepository(testRepo);
RepoPackage testRepoPackage =
new RPMRepoPackage(NAME, VERSION1, RELEASE1, ARCHITECTURE, testRepo);
Set<IMABaselineRecord> imaRecords = new HashSet<>();
imaRecords.add(SimpleImaBaselineTest.createTestIMARecord(FILEPATH1));
testRepoPackage.setAllMeasurements(imaRecords, RepoPackageTest.getTestDigest());
repoManager.saveRepoPackage(testRepoPackage);
Set<Repository<?>> originalRepositories = new HashSet<>();
originalRepositories.add(testRepo);
testBaseline.setRepositories(originalRepositories);
testBaseline.update(repoManager);
DBBaselineManager baselineManager = new DBBaselineManager(sessionFactory);
BroadRepoImaBaseline savedBaseline =
(BroadRepoImaBaseline) baselineManager.save(testBaseline);
IMABaselineRecord baselineRecord = SimpleImaBaselineTest.createTestIMARecord(FILEPATH1);
IMAMeasurementRecord measurementRecord = new IMAMeasurementRecord(
baselineRecord.getPath(),
baselineRecord.getHash()
);
Assert.assertEquals(
savedBaseline.containsHashes(
Collections.singletonList(measurementRecord),
new DbImaBaselineRecordManager(sessionFactory),
SimpleImaBaselineTest.getTestImaPolicy(false)
).getIMAMatchStatuses(measurementRecord),
Collections.singleton(
new IMAMatchStatus<>(
measurementRecord, ReportMatchStatus.MATCH, baselineRecord, baseline
)
)
);
measurementRecord = new IMAMeasurementRecord(
"/some/other/file",
baselineRecord.getHash()
);
Assert.assertEquals(
savedBaseline.containsHashes(
Collections.singletonList(measurementRecord),
new DbImaBaselineRecordManager(sessionFactory),
SimpleImaBaselineTest.getTestImaPolicy(false)
).getIMAMatchStatuses(measurementRecord),
Collections.singleton(
new IMAMatchStatus<>(
measurementRecord, ReportMatchStatus.MATCH, baselineRecord, baseline
)
)
);
}
/**
* Test that ensures a BroadRepoImaBaseline can correctly determine that
* it does not contain any matching baseline records solely based upon a given measurement
* record's hash.
*
* @throws UnsupportedEncodingException
* if an error is encountered while getting the test digest
*/
@Test
public final void containsHashesWithNoMatches() throws UnsupportedEncodingException {
BroadRepoImaBaseline testBaseline = new BroadRepoImaBaseline(BASELINE_NAME);
Repository testRepo = new TestRepository("Test Repository", 0);
DBRepositoryManager repoManager = new DBRepositoryManager(sessionFactory);
testRepo = repoManager.saveRepository(testRepo);
RepoPackage testRepoPackage =
new RPMRepoPackage(NAME, VERSION1, RELEASE1, ARCHITECTURE, testRepo);
Set<IMABaselineRecord> imaRecords = new HashSet<>();
imaRecords.add(SimpleImaBaselineTest.createTestIMARecord(FILEPATH1));
testRepoPackage.setAllMeasurements(imaRecords, RepoPackageTest.getTestDigest());
repoManager.saveRepoPackage(testRepoPackage);
Set<Repository<?>> originalRepositories = new HashSet<>();
originalRepositories.add(testRepo);
testBaseline.setRepositories(originalRepositories);
testBaseline.update(repoManager);
DBBaselineManager baselineManager = new DBBaselineManager(sessionFactory);
BroadRepoImaBaseline savedBaseline =
(BroadRepoImaBaseline) baselineManager.save(testBaseline);
IMABaselineRecord baselineRecord = SimpleImaBaselineTest.createTestIMARecord(FILEPATH1);
IMAMeasurementRecord measurementRecord = new IMAMeasurementRecord(
baselineRecord.getPath(),
SimpleImaBaselineTest.getDigest("0d5f3c2f7f3003d2e4baddc46ed4763a4954f648")
);
Assert.assertEquals(
savedBaseline.containsHashes(
Collections.singletonList(measurementRecord),
new DbImaBaselineRecordManager(sessionFactory),
SimpleImaBaselineTest.getTestImaPolicy(false)
),
new BatchImaMatchStatus<>(Collections.singleton(new IMAMatchStatus<>(
measurementRecord,
ReportMatchStatus.UNKNOWN,
baseline
)))
);
}
/**
* Tests that the <code>Set</code> of <code>Repositories</code>s associated with this baseline
* can be set, retrieved, and returned.

View File

@ -1,5 +1,6 @@
package hirs.data.persist;
import hirs.ima.matching.BatchImaMatchStatus;
import hirs.ima.matching.IMAMatchStatus;
import hirs.persist.BaselineManager;
import hirs.persist.DBBaselineManager;
@ -223,6 +224,15 @@ public class SimpleImaBaselineTest extends SpringPersistenceTest {
).getIMAMatchStatuses(record).iterator().next();
}
private BatchImaMatchStatus<IMABaselineRecord> baselineContainsHashes(
final SimpleImaBaseline baseline,
final IMAMeasurementRecord record,
final IMAPolicy imaPolicy) {
return baseline.containsHashes(
Collections.singletonList(record), recordManager, imaPolicy
);
}
/**
* Create a test IMAPolicy object.
*
@ -601,6 +611,91 @@ public class SimpleImaBaselineTest extends SpringPersistenceTest {
}
}
/**
* Simple test that ensures a SimpleImaBaseline can determine whether it contains
* baseline records that match measurement records based solely on their hashes.
*/
@Test
public final void containsHashes() {
final SimpleImaBaseline baseline = new SimpleImaBaseline("TestBaseline");
final String baselineGradleFilename = "/usr/bin/gradle";
final String measuredGradleFilename = "/usr/bin/gradle_by_another_name";
final Digest gradleHash = getDigest("33333c2f7f3003d2e4baddc46ed4763a49543333");
final IMABaselineRecord baseRecSameNameMatchingHash = new IMABaselineRecord(
measuredGradleFilename, gradleHash
);
final IMABaselineRecord baseRecDifferentNameMatchingHash = new IMABaselineRecord(
baselineGradleFilename, gradleHash
);
baseline.addToBaseline(baseRecSameNameMatchingHash);
baseline.addToBaseline(baseRecDifferentNameMatchingHash);
baseline.addToBaseline(new IMABaselineRecord(
baselineGradleFilename,
getDigest("00000c2f7f3003d2e4baddc46ed4763a49543333")
));
baseline.addToBaseline(new IMABaselineRecord(
measuredGradleFilename,
getDigest("00000c2f7f3003d2e4baddc46ed4763a49543333")
));
IMAMeasurementRecord measurementRecord = new IMAMeasurementRecord(
measuredGradleFilename, gradleHash
);
Set<IMABaselineRecord> matchingRecords = new HashSet<>(Arrays.asList(
baseRecSameNameMatchingHash,
baseRecDifferentNameMatchingHash
));
Assert.assertEquals(
baselineContainsHashes(baseline, measurementRecord, getTestImaPolicy(false)),
new BatchImaMatchStatus<>(
Collections.singleton(new IMAMatchStatus<>(
measurementRecord,
ReportMatchStatus.MATCH,
matchingRecords,
baseline
))
)
);
}
/**
* Simple test that ensures a SimpleImaBaseline can determine whether it contains
* baseline records that match measurement records based solely on their hashes.
*/
@Test
public final void containsHashesWithNoMatches() {
final SimpleImaBaseline baseline = new SimpleImaBaseline("TestBaseline");
final String baselineGradleFilename = "/usr/bin/gradle";
final String measuredGradleFilename = "/usr/bin/gradle_by_another_name";
final Digest gradleHash = getDigest("33333c2f7f3003d2e4baddc46ed4763a49543333");
baseline.addToBaseline(new IMABaselineRecord(
baselineGradleFilename,
getDigest("00000c2f7f3003d2e4baddc46ed4763a49543333")
));
IMAMeasurementRecord record = new IMAMeasurementRecord(
measuredGradleFilename, gradleHash
);
Assert.assertEquals(baselineContainsHashes(baseline, record, getTestImaPolicy(false)),
new BatchImaMatchStatus<>(
Collections.singleton(new IMAMatchStatus<>(
record,
ReportMatchStatus.UNKNOWN,
baseline
))
)
);
}
/**
* Tests that getBaselineRecords() returns a list of IMA records.
*

View File

@ -8,6 +8,7 @@ import java.util.HashSet;
import java.util.List;
import java.util.Set;
import hirs.ima.matching.BatchImaMatchStatus;
import hirs.ima.matching.IMAMatchStatus;
import hirs.persist.BaselineManager;
import hirs.persist.DBBaselineManager;
@ -355,6 +356,123 @@ public class TargetedRepoImaBaselineTest extends SpringPersistenceTest {
);
}
/**
* Test that ensures a TargetedRepoImaBaseline can correctly determine if
* it contains any matching baseline records solely based upon a given measurement
* record's hash.
*
* @throws UnsupportedEncodingException
* if an error is encountered while getting the test digest
*/
@Test
public final void containsHashes() throws UnsupportedEncodingException {
TargetedRepoImaBaseline testBaseline = new TargetedRepoImaBaseline(BASELINE_NAME);
Repository testRepo = new TestRepository("Test Repository", 0);
DBRepositoryManager repoManager = new DBRepositoryManager(sessionFactory);
testRepo = repoManager.saveRepository(testRepo);
RepoPackage testRepoPackage =
new RPMRepoPackage(NAME, VERSION1, RELEASE1, ARCHITECTURE, testRepo);
Set<IMABaselineRecord> imaRecords = new HashSet<>();
imaRecords.add(SimpleImaBaselineTest.createTestIMARecord(FILEPATH1));
testRepoPackage.setAllMeasurements(imaRecords, RepoPackageTest.getTestDigest());
repoManager.saveRepoPackage(testRepoPackage);
Set<RepoPackage> originalPackages = new HashSet<>();
originalPackages.add(testRepoPackage);
testBaseline.setRepoPackages(originalPackages);
DBBaselineManager baselineManager = new DBBaselineManager(sessionFactory);
TargetedRepoImaBaseline savedBaseline =
(TargetedRepoImaBaseline) baselineManager.save(testBaseline);
IMABaselineRecord baselineRecord = SimpleImaBaselineTest.createTestIMARecord(FILEPATH1);
IMAMeasurementRecord measurementRecord = new IMAMeasurementRecord(
baselineRecord.getPath(),
baselineRecord.getHash()
);
Assert.assertEquals(
savedBaseline.containsHashes(
Collections.singletonList(measurementRecord),
new DbImaBaselineRecordManager(sessionFactory),
SimpleImaBaselineTest.getTestImaPolicy(false)
).getIMAMatchStatuses(measurementRecord),
Collections.singleton(
new IMAMatchStatus<>(
measurementRecord, ReportMatchStatus.MATCH, baselineRecord, baseline
)
)
);
measurementRecord = new IMAMeasurementRecord(
"/some/other/file",
baselineRecord.getHash()
);
Assert.assertEquals(
savedBaseline.containsHashes(
Collections.singletonList(measurementRecord),
new DbImaBaselineRecordManager(sessionFactory),
SimpleImaBaselineTest.getTestImaPolicy(false)
).getIMAMatchStatuses(measurementRecord),
Collections.singleton(
new IMAMatchStatus<>(
measurementRecord, ReportMatchStatus.MATCH, baselineRecord, baseline
)
)
);
}
/**
* Test that ensures a TargetedRepoImaBaseline can correctly determine that
* it does not contain any matching baseline records solely based upon a given measurement
* record's hash.
*
* @throws UnsupportedEncodingException
* if an error is encountered while getting the test digest
*/
@Test
public final void containsHashesWithNoMatches() throws UnsupportedEncodingException {
TargetedRepoImaBaseline testBaseline = new TargetedRepoImaBaseline(BASELINE_NAME);
Repository testRepo = new TestRepository("Test Repository", 0);
DBRepositoryManager repoManager = new DBRepositoryManager(sessionFactory);
testRepo = repoManager.saveRepository(testRepo);
RepoPackage testRepoPackage =
new RPMRepoPackage(NAME, VERSION1, RELEASE1, ARCHITECTURE, testRepo);
Set<IMABaselineRecord> imaRecords = new HashSet<>();
imaRecords.add(SimpleImaBaselineTest.createTestIMARecord(FILEPATH1));
testRepoPackage.setAllMeasurements(imaRecords, RepoPackageTest.getTestDigest());
repoManager.saveRepoPackage(testRepoPackage);
Set<RepoPackage> originalPackages = new HashSet<>();
originalPackages.add(testRepoPackage);
testBaseline.setRepoPackages(originalPackages);
DBBaselineManager baselineManager = new DBBaselineManager(sessionFactory);
TargetedRepoImaBaseline savedBaseline =
(TargetedRepoImaBaseline) baselineManager.save(testBaseline);
IMABaselineRecord baselineRecord = SimpleImaBaselineTest.createTestIMARecord(FILEPATH1);
IMAMeasurementRecord measurementRecord = new IMAMeasurementRecord(
baselineRecord.getPath(),
SimpleImaBaselineTest.getDigest("0d5f3c2f7f3003d2e4baddc46ed4763a4954f648")
);
Assert.assertEquals(
savedBaseline.containsHashes(
Collections.singletonList(measurementRecord),
new DbImaBaselineRecordManager(sessionFactory),
SimpleImaBaselineTest.getTestImaPolicy(false)
),
new BatchImaMatchStatus<>(Collections.singleton(new IMAMatchStatus<>(
measurementRecord,
ReportMatchStatus.UNKNOWN,
baseline
)))
);
}
/**
* Tests that a <code>TargetedRepoImaBaseline</code> can be archived.
*/

View File

@ -0,0 +1,153 @@
package hirs.ima.matching;
import hirs.data.persist.Digest;
import hirs.data.persist.IMABaselineRecord;
import hirs.data.persist.IMAMeasurementRecord;
import hirs.data.persist.ReportMatchStatus;
import hirs.data.persist.SimpleImaBaseline;
import hirs.data.persist.SimpleImaBaselineTest;
import org.testng.Assert;
import org.testng.annotations.Test;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashSet;
import java.util.Set;
/**
* Tests for the ImaAcceptableHashRecordMatcher. These are basic tests of its functionality;
* more complete tests for contains() as used operationally by baselines that test various
* permutations of parameters are located in SimpleImaBaselineTest, BroadRepoImaBaselineTest,
* TargetedRepoImaBaselineTest, ImaBlacklistBaselineTest, and ImaIgnoreSetBaselineTest.
*/
public class ImaAcceptableHashRecordMatcherTest {
private static final String FILENAME_1 = "/bin/ls";
private static final String FILENAME_2 = "/bin/ls_with_another_name";
private static final Digest HASH_1 =
SimpleImaBaselineTest.getDigest("33333c2f7f3003d2e4baddc46ed4763a49543333");
private static final Digest HASH_2 =
SimpleImaBaselineTest.getDigest("00000c2f7f3003d2e4baddc46ed4763a49543333");
/**
* Tests that the 'contains' method functions if no records are given.
*/
@Test
public void testContainsEmpty() {
SimpleImaBaseline baseline = getTestSimpleImaBaseline();
IMAMeasurementRecord measurementRecord = new IMAMeasurementRecord(FILENAME_1, HASH_1);
Assert.assertEquals(
new ImaAcceptableHashRecordMatcher(
Collections.emptyList(),
SimpleImaBaselineTest.getTestImaPolicy(false),
baseline).contains(measurementRecord),
new IMAMatchStatus(measurementRecord, ReportMatchStatus.UNKNOWN, baseline)
);
}
/**
* Tests that the 'contains' method functions if a matching record is given in the case of that
* matching record being both filename and hash.
*/
@Test
public void testContainsSameFilename() {
SimpleImaBaseline baseline = getTestSimpleImaBaseline();
IMAMeasurementRecord measurementRecord = new IMAMeasurementRecord(FILENAME_1, HASH_1);
IMABaselineRecord baselineRecord = new IMABaselineRecord(FILENAME_1, HASH_1);
Assert.assertEquals(
new ImaAcceptableHashRecordMatcher(
Collections.singletonList(baselineRecord),
SimpleImaBaselineTest.getTestImaPolicy(false),
baseline).contains(measurementRecord),
new IMAMatchStatus<>(
measurementRecord, ReportMatchStatus.MATCH, baselineRecord, baseline
)
);
}
/**
* Tests that the 'contains' method matches on hash properly, even if
* a measurement record has a different filename than the matching baseline record.
*/
@Test
public void testContainsDifferentFilename() {
SimpleImaBaseline baseline = getTestSimpleImaBaseline();
IMAMeasurementRecord measurementRecord = new IMAMeasurementRecord(FILENAME_2, HASH_1);
IMABaselineRecord baselineRecord = new IMABaselineRecord(FILENAME_1, HASH_1);
Assert.assertEquals(
new ImaAcceptableHashRecordMatcher(
Collections.singletonList(baselineRecord),
SimpleImaBaselineTest.getTestImaPolicy(false),
baseline).contains(measurementRecord),
new IMAMatchStatus<>(
measurementRecord, ReportMatchStatus.MATCH, baselineRecord, baseline
)
);
}
/**
* Tests that the 'contains' method returns the expected 'UNKNOWN' match
* status if no record matches the collected measurement in a nonempty baseline.
*/
@Test
public void testContainsNonEmptyButUnknown() {
SimpleImaBaseline baseline = getTestSimpleImaBaseline();
IMAMeasurementRecord measurementRecord = new IMAMeasurementRecord(FILENAME_1, HASH_1);
IMABaselineRecord baselineRecord = new IMABaselineRecord(FILENAME_1, HASH_2);
Assert.assertEquals(
new ImaAcceptableHashRecordMatcher(
Collections.singletonList(baselineRecord),
SimpleImaBaselineTest.getTestImaPolicy(false),
baseline).contains(measurementRecord),
new IMAMatchStatus(measurementRecord, ReportMatchStatus.UNKNOWN, baseline)
);
}
/**
* Tests that the 'contains' method returns all matching baseline records from a
* baseline when there are multiple matches to a given measurement record.
*/
@Test
public void testContainsMultipleMatchingBaselineRecords() {
SimpleImaBaseline baseline = getTestSimpleImaBaseline();
IMAMeasurementRecord measurementRecord = new IMAMeasurementRecord(FILENAME_1, HASH_1);
Set<IMABaselineRecord> baselineRecords = new HashSet<>(Arrays.asList(
new IMABaselineRecord(FILENAME_1, HASH_1),
new IMABaselineRecord(FILENAME_2, HASH_1),
new IMABaselineRecord(FILENAME_1, HASH_2)
));
Assert.assertEquals(
new ImaAcceptableHashRecordMatcher(
baselineRecords,
SimpleImaBaselineTest.getTestImaPolicy(false),
baseline
).contains(measurementRecord),
new IMAMatchStatus<>(
measurementRecord,
ReportMatchStatus.MATCH,
new HashSet<>(Arrays.asList(
new IMABaselineRecord(FILENAME_1, HASH_1),
new IMABaselineRecord(FILENAME_2, HASH_1))
),
baseline
)
);
}
/**
* Tests that the 'contains' method throws an IllegalArgumentException if given a null
* measurement record to check.
*/
@Test(expectedExceptions = IllegalArgumentException.class)
public void testContainsOnNullRecord() {
SimpleImaBaseline baseline = getTestSimpleImaBaseline();
IMABaselineRecord baselineRecord = new IMABaselineRecord(FILENAME_1, HASH_1);
new ImaAcceptableHashRecordMatcher(
Collections.singletonList(baselineRecord),
SimpleImaBaselineTest.getTestImaPolicy(false),
baseline).contains(null);
}
private static SimpleImaBaseline getTestSimpleImaBaseline() {
return new SimpleImaBaseline("Test IMA Baseline");
}
}

View File

@ -1,26 +1,27 @@
package hirs.ima;
package hirs.ima.matching;
import hirs.data.persist.Digest;
import hirs.data.persist.DigestTest;
import hirs.data.persist.IMABaselineRecord;
import hirs.data.persist.IMAMeasurementRecord;
import hirs.data.persist.ImaAcceptableRecordBaseline;
import hirs.data.persist.ReportMatchStatus;
import hirs.data.persist.SimpleImaBaseline;
import hirs.ima.matching.IMAMatchStatus;
import hirs.ima.matching.ImaAcceptableRecordMatcher;
import hirs.data.persist.SimpleImaBaselineTest;
import org.testng.Assert;
import org.testng.annotations.Test;
import hirs.data.persist.SimpleImaBaselineTest;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
/**
* Tests ImaAcceptableRecordMatcher. These are very basic tests of its functionality;
* Tests ImaAcceptablePathAndHashRecordMatcher. These are very basic tests of its functionality;
* more complete tests for contains() as used operationally by baselines that test various
* permutations of parameters are located in SimpleImaBaselineTest, BroadRepoImaBaselineTest,
* TargetedRepoImaBaselineTest, ImaBlacklistBaselineTest, and ImaIgnoreSetBaselineTest.
*/
public class ImaAcceptableRecordMatcherTest {
public class ImaAcceptablePathAndHashRecordMatcherTest {
private static final String FILE_1 = "/bin/ls";
private static final String PARTIAL_FILE_1 = "ls";
private static final Digest HASH_1 =
@ -33,6 +34,7 @@ public class ImaAcceptableRecordMatcherTest {
private static final Digest LIB64_LD_HASH =
SimpleImaBaselineTest.getDigest("55555c2f7f3003d2e4baddc46ed4763a49543333");
private static final Digest ONES = DigestTest.getTestSHA1Digest();
/**
* Tests that contains functions if no records are given.
@ -42,7 +44,7 @@ public class ImaAcceptableRecordMatcherTest {
SimpleImaBaseline baseline = getTestSimpleImaBaseline();
IMAMeasurementRecord measurementRecord = new IMAMeasurementRecord(FILE_1, HASH_1);
Assert.assertEquals(
new ImaAcceptableRecordMatcher(
new ImaAcceptablePathAndHashRecordMatcher(
Collections.emptyList(),
SimpleImaBaselineTest.getTestImaPolicy(false),
baseline).contains(measurementRecord),
@ -59,7 +61,7 @@ public class ImaAcceptableRecordMatcherTest {
IMAMeasurementRecord measurementRecord = new IMAMeasurementRecord(FILE_1, HASH_1);
IMABaselineRecord baselineRecord = new IMABaselineRecord(FILE_1, HASH_1);
Assert.assertEquals(
new ImaAcceptableRecordMatcher(
new ImaAcceptablePathAndHashRecordMatcher(
Collections.singletonList(baselineRecord),
SimpleImaBaselineTest.getTestImaPolicy(false),
baseline).contains(measurementRecord),
@ -78,7 +80,7 @@ public class ImaAcceptableRecordMatcherTest {
IMAMeasurementRecord measurementRecord = new IMAMeasurementRecord(PARTIAL_FILE_1, HASH_1);
IMABaselineRecord baselineRecord = new IMABaselineRecord(FILE_1, HASH_1);
Assert.assertEquals(
new ImaAcceptableRecordMatcher(
new ImaAcceptablePathAndHashRecordMatcher(
Collections.singletonList(baselineRecord),
SimpleImaBaselineTest.getTestImaPolicy(true),
baseline).contains(measurementRecord),
@ -99,16 +101,82 @@ public class ImaAcceptableRecordMatcherTest {
IMAMeasurementRecord measurementRecord =
new IMAMeasurementRecord(LIB64_LD_FILE, USR_LIB64_LD_HASH);
Assert.assertEquals(
new ImaAcceptableRecordMatcher(
new ImaAcceptablePathAndHashRecordMatcher(
Collections.singletonList(baselineRecord),
SimpleImaBaselineTest.getTestImaPolicy(false),
baseline).contains(measurementRecord),
baseline
).contains(measurementRecord),
new IMAMatchStatus<>(
measurementRecord, ReportMatchStatus.MATCH, baselineRecord, baseline
)
);
}
/**
* Tests that contains correctly matches equivalent paths for real-world
* examples that have been seen.
*/
@Test
public void testContainsExperiencedEquivalentPaths() {
List<List<String>> pairs = Arrays.asList(
Arrays.asList("/usr/sbin/dhclient", "/sbin/dhclient"),
Arrays.asList("/usr/sbin/sysctl", "/sbin/sysctl"),
Arrays.asList("/usr/sbin/swapon", "/sbin/swapon"),
Arrays.asList("/sbin/audispd", "/usr/sbin/audispd"),
Arrays.asList("/usr/sbin/sysctl", "/sbin/sysctl"),
Arrays.asList("/sbin/ldconfig", "/usr/sbin/ldconfig"),
Arrays.asList("/sbin/kexec", "/usr/sbin/kexec"),
Arrays.asList("/usr/sbin/ip", "/sbin/ip"),
Arrays.asList("/usr/bin/bash", "/bin/bash")
);
for (List<String> pair : pairs) {
testEquivalentNames(pair.get(0), pair.get(1));
}
}
/**
* Tests that contains correctly matches equivalent paths.
*/
@Test
public void testContainsExhaustiveEquivalentPaths() {
List<List<String>> pairs = Arrays.asList(
Arrays.asList("/bin/foofile", "/usr/bin/foofile"),
Arrays.asList("/lib/foofile", "/usr/lib/foofile"),
Arrays.asList("/lib64/foofile", "/usr/lib64/foofile"),
Arrays.asList("/usr/bin/foofile", "/usr/sbin/foofile"),
Arrays.asList("/sbin/foofile", "/usr/sbin/foofile")
);
for (int i = 0; i < pairs.size(); i++) {
List<String> equivPair = pairs.get(i);
testEquivalentNames(equivPair.get(0), equivPair.get(1));
testEquivalentNames(equivPair.get(1), equivPair.get(0));
}
}
private void testEquivalentNames(final String measuredFilename, final String baselineFilename) {
final IMAMeasurementRecord measurementRecord =
new IMAMeasurementRecord(measuredFilename, ONES);
final IMABaselineRecord baselineRecord = new IMABaselineRecord(baselineFilename, ONES);
final ImaAcceptableRecordBaseline baseline = getTestSimpleImaBaseline();
Assert.assertEquals(
new ImaAcceptablePathAndHashRecordMatcher(
Collections.singleton(baselineRecord),
SimpleImaBaselineTest.getTestImaPolicy(false),
baseline
).contains(measurementRecord),
new IMAMatchStatus<>(
measurementRecord,
ReportMatchStatus.MATCH,
baselineRecord,
baseline
)
);
}
/**
* This tests a case where a baseline includes a file at both /lib64 and /usr/lib64
* with distinct hashes, and a report contains an entry for a file at /usr/lib64 whose hash
@ -128,7 +196,7 @@ public class ImaAcceptableRecordMatcherTest {
IMAMeasurementRecord measurementRecord =
new IMAMeasurementRecord(USR_LIB64_LD_FILE, LIB64_LD_HASH);
Assert.assertEquals(
new ImaAcceptableRecordMatcher(
new ImaAcceptablePathAndHashRecordMatcher(
Arrays.asList(libBaselineRecord, usrLibBaselineRecord),
SimpleImaBaselineTest.getTestImaPolicy(false),
baseline).contains(measurementRecord),

View File

@ -0,0 +1,5 @@
/**
* Contains classes that perform record matching from IMA measurement records
* to IMA baselines records.
*/
package hirs.ima.matching;