Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
using System.Threading.Tasks;
using Microsoft.Sbom.Api.Entities;
using Microsoft.Sbom.Api.Manifest.FileHashes;
using Microsoft.Sbom.Common.Config;
using Microsoft.Sbom.Contracts.Enums;
using Microsoft.Sbom.Extensions.Entities;

Expand All @@ -19,12 +20,16 @@ namespace Microsoft.Sbom.Api.Executors;
public class ConcurrentSha256HashValidator
{
private readonly FileHashesDictionary fileHashesDictionary;
private readonly IConfiguration configuration;

public ConcurrentSha256HashValidator(FileHashesDictionary fileHashesDictionary)
public ConcurrentSha256HashValidator(FileHashesDictionary fileHashesDictionary, IConfiguration configuration)
{
this.fileHashesDictionary = fileHashesDictionary ?? throw new ArgumentNullException(nameof(fileHashesDictionary));
this.configuration = configuration ?? throw new ArgumentNullException(nameof(configuration));
}

private AlgorithmName HashAlgorithmName => configuration.HashAlgorithm?.Value ?? AlgorithmName.SHA256;

public (ChannelReader<FileValidationResult> output, ChannelReader<FileValidationResult> errors)
Validate(ChannelReader<InternalSbomFileInfo> fileWithHash)
{
Expand All @@ -47,9 +52,9 @@ public ConcurrentSha256HashValidator(FileHashesDictionary fileHashesDictionary)

private async Task Validate(InternalSbomFileInfo internalFileInfo, Channel<FileValidationResult> output, Channel<FileValidationResult> errors)
{
var sha256Checksum = internalFileInfo.Checksum.FirstOrDefault(c => c.Algorithm == AlgorithmName.SHA256);
var checksum = internalFileInfo.Checksum.FirstOrDefault(c => c.Algorithm == HashAlgorithmName);
var fileHashes = new FileHashes();
fileHashes.SetHash(internalFileInfo.FileLocation, sha256Checksum);
fileHashes.SetHash(internalFileInfo.FileLocation, checksum);
FileValidationResult failureResult = null;

var newValue = fileHashesDictionary.FileHashes.AddOrUpdate(internalFileInfo.Path, fileHashes, (key, oldValue) =>
Expand All @@ -66,7 +71,7 @@ private async Task Validate(InternalSbomFileInfo internalFileInfo, Channel<FileV
return null;
}

oldValue?.SetHash(internalFileInfo.FileLocation, sha256Checksum);
oldValue?.SetHash(internalFileInfo.FileLocation, checksum);
return oldValue;
});

Expand All @@ -79,7 +84,9 @@ private async Task Validate(InternalSbomFileInfo internalFileInfo, Channel<FileV
// If we have the files from both locations present in the hash, validate if the hashes match.
if (newValue?.FileLocation == Sbom.Entities.FileLocation.All)
{
if (string.Equals(newValue.OnDiskHash?.ChecksumValue, newValue.SbomFileHash?.ChecksumValue, StringComparison.InvariantCultureIgnoreCase))
// A missing hash on either side means there is nothing to compare, so it cannot count as a match.
if (!string.IsNullOrEmpty(newValue.OnDiskHash?.ChecksumValue) &&
string.Equals(newValue.OnDiskHash?.ChecksumValue, newValue.SbomFileHash?.ChecksumValue, StringComparison.InvariantCultureIgnoreCase))
{
await output.Writer.WriteAsync(new FileValidationResult { Path = internalFileInfo.Path });
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ public SbomValidationWorkflowFactory(
public IWorkflow<SbomParserBasedValidationWorkflow> Get(IConfiguration configuration, ISbomConfig sbomConfig, string eventName)
{
var fileHashesDictionary = new FileHashesDictionary(new System.Collections.Concurrent.ConcurrentDictionary<string, FileHashes>(osUtils.GetFileSystemStringComparer()));
var hashValidator = new ConcurrentSha256HashValidator(fileHashesDictionary);
var hashValidator = new ConcurrentSha256HashValidator(fileHashesDictionary, configuration);
var filesValidator = new FilesValidator(directoryWalker, configuration, log, fileHasher, fileFilterer, hashValidator, enumeratorChannel, fileConverter, fileHashesDictionary, spdxFileFilterer);
return new SbomParserBasedValidationWorkflow(recorder, signValidationProvider, log, manifestParserProvider, configuration, sbomConfig, filesValidator, validationResultGenerator, outputWriter, fileSystemUtils, osUtils, eventName);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.

using System;
using System.Collections.Concurrent;
using System.Threading.Channels;
using System.Threading.Tasks;
using Microsoft.Sbom.Api.Manifest.FileHashes;
using Microsoft.Sbom.Common.Config;
using Microsoft.Sbom.Contracts;
using Microsoft.Sbom.Contracts.Enums;
using Microsoft.Sbom.Extensions.Entities;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Moq;
using ErrorType = Microsoft.Sbom.Api.Entities.ErrorType;
using FileLocation = Microsoft.Sbom.Entities.FileLocation;

namespace Microsoft.Sbom.Api.Executors.Tests;

[TestClass]
public class ConcurrentSha256HashValidatorTests
{
[TestMethod]
public async Task ChecksumForConfiguredAlgorithm_MatchesAsync()
{
var validationResults = BuildValidator(AlgorithmName.SHA256).Validate(await BuildFilesAsync(AlgorithmName.SHA256));

var validatedCount = 0;
await foreach (var output in validationResults.output.ReadAllAsync())
{
validatedCount++;
Assert.AreEqual("/test/file", output.Path);
}

var errorCount = 0;
await foreach (var error in validationResults.errors.ReadAllAsync())
{
errorCount++;
}

Assert.AreEqual(1, validatedCount);
Assert.AreEqual(0, errorCount);
}

[TestMethod]
public async Task ChecksumMissingForConfiguredAlgorithm_DoesNotMatchAsync()
{
var validationResults = BuildValidator(AlgorithmName.SHA1).Validate(await BuildFilesAsync(AlgorithmName.SHA256));

var validatedCount = 0;
await foreach (var output in validationResults.output.ReadAllAsync())
{
validatedCount++;
}

var errorCount = 0;
await foreach (var error in validationResults.errors.ReadAllAsync())
{
errorCount++;
Assert.AreEqual(ErrorType.InvalidHash, error.ErrorType);
}

Assert.AreEqual(0, validatedCount);
Assert.AreEqual(1, errorCount);
}

private static ConcurrentSha256HashValidator BuildValidator(AlgorithmName configuredAlgorithm)
{
var configuration = new Mock<IConfiguration>();
configuration.SetupGet(c => c.HashAlgorithm).Returns(new ConfigurationSetting<AlgorithmName> { Value = configuredAlgorithm });

var fileHashes = new FileHashesDictionary(new ConcurrentDictionary<string, FileHashes>(StringComparer.InvariantCultureIgnoreCase));
return new ConcurrentSha256HashValidator(fileHashes, configuration.Object);
}

private static async Task<ChannelReader<InternalSbomFileInfo>> BuildFilesAsync(AlgorithmName checksumAlgorithm)
{
var files = Channel.CreateUnbounded<InternalSbomFileInfo>();
foreach (var location in new[] { FileLocation.OnDisk, FileLocation.InSbomFile })
{
await files.Writer.WriteAsync(new InternalSbomFileInfo
{
Path = "/test/file",
FileLocation = location,
Checksum = new Checksum[] { new Checksum { Algorithm = checksumAlgorithm, ChecksumValue = "hash" } }
});
}

files.Writer.Complete();
return files;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -164,7 +164,7 @@ public async Task SbomParserBasedValidationWorkflowTests_ReturnsSuccessAndValida
var osUtilsMock = new Mock<IOSUtils>(MockBehavior.Strict);

var fileHash = BuildFileHash();
var hashValidator = new ConcurrentSha256HashValidator(fileHash);
var hashValidator = new ConcurrentSha256HashValidator(fileHash, configurationMock.Object);
var enumeratorChannel = new EnumeratorChannel(mockLogger.Object);
var fileConverter = new SbomFileToFileInfoConverter(new FileTypeUtils());
var spdxFileFilterer = new FileFilterer(rootFileFilterMock, mockLogger.Object, configurationMock.Object, fileSystemMock.Object);
Expand Down Expand Up @@ -342,7 +342,7 @@ public async Task SbomParserBasedValidationWorkflowTests_ReturnsSuccessAndValida
osUtilsMock.Setup(x => x.IsCaseSensitiveOS()).Returns(false);

var fileHash = BuildFileHash();
var hashValidator = new ConcurrentSha256HashValidator(fileHash);
var hashValidator = new ConcurrentSha256HashValidator(fileHash, configurationMock.Object);
var enumeratorChannel = new EnumeratorChannel(mockLogger.Object);
var fileConverter = new SbomFileToFileInfoConverter(new FileTypeUtils());
var spdxFileFilterer = new FileFilterer(rootFileFilterMock, mockLogger.Object, configurationMock.Object, fileSystemMock.Object);
Expand Down Expand Up @@ -535,7 +535,7 @@ private FilesValidator GetFilesValidator(Mock<IFileSystemUtils> fileSystemMock,
rootFileFilterMock.Init();

var fileHash = BuildFileHash();
var hashValidator = new ConcurrentSha256HashValidator(fileHash);
var hashValidator = new ConcurrentSha256HashValidator(fileHash, configurationMock.Object);
var enumeratorChannel = new EnumeratorChannel(mockLogger.Object);
var fileConverter = new SbomFileToFileInfoConverter(new FileTypeUtils());
var spdxFileFilterer = new FileFilterer(rootFileFilterMock, mockLogger.Object, configurationMock.Object, fileSystemMock.Object);
Expand Down