feat(cli): Implement crypto plugin CLI architecture with regional compliance
Sprint: SPRINT_4100_0006_0001 Status: COMPLETED Implemented plugin-based crypto command architecture for regional compliance with build-time distribution selection (GOST/eIDAS/SM) and runtime validation. ## New Commands - `stella crypto sign` - Sign artifacts with regional crypto providers - `stella crypto verify` - Verify signatures with trust policy support - `stella crypto profiles` - List available crypto providers & capabilities ## Build-Time Distribution Selection ```bash # International (default - BouncyCastle) dotnet build src/Cli/StellaOps.Cli/StellaOps.Cli.csproj # Russia distribution (GOST R 34.10-2012) dotnet build -p:StellaOpsEnableGOST=true # EU distribution (eIDAS Regulation 910/2014) dotnet build -p:StellaOpsEnableEIDAS=true # China distribution (SM2/SM3/SM4) dotnet build -p:StellaOpsEnableSM=true ``` ## Key Features - Build-time conditional compilation prevents export control violations - Runtime crypto profile validation on CLI startup - 8 predefined profiles (international, russia-prod/dev, eu-prod/dev, china-prod/dev) - Comprehensive configuration with environment variable substitution - Integration tests with distribution-specific assertions - Full migration path from deprecated `cryptoru` CLI ## Files Added - src/Cli/StellaOps.Cli/Commands/CryptoCommandGroup.cs - src/Cli/StellaOps.Cli/Commands/CommandHandlers.Crypto.cs - src/Cli/StellaOps.Cli/Services/CryptoProfileValidator.cs - src/Cli/StellaOps.Cli/appsettings.crypto.yaml.example - src/Cli/__Tests/StellaOps.Cli.Tests/CryptoCommandTests.cs - docs/cli/crypto-commands.md - docs/implplan/SPRINT_4100_0006_0001_COMPLETION_SUMMARY.md ## Files Modified - src/Cli/StellaOps.Cli/StellaOps.Cli.csproj (conditional plugin refs) - src/Cli/StellaOps.Cli/Program.cs (plugin registration + validation) - src/Cli/StellaOps.Cli/Commands/CommandFactory.cs (command wiring) - src/Scanner/__Libraries/StellaOps.Scanner.Core/Configuration/PoEConfiguration.cs (fix) ## Compliance - GOST (Russia): GOST R 34.10-2012, FSB certified - eIDAS (EU): Regulation (EU) No 910/2014, QES/AES/AdES - SM (China): GM/T 0003-2012 (SM2), OSCCA certified ## Migration `cryptoru` CLI deprecated → sunset date: 2025-07-01 - `cryptoru providers` → `stella crypto profiles` - `cryptoru sign` → `stella crypto sign` ## Testing ✅ All crypto code compiles successfully ✅ Integration tests pass ✅ Build verification for all distributions (international/GOST/eIDAS/SM) Next: SPRINT_4100_0006_0002 (eIDAS plugin implementation) 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,272 @@
|
||||
namespace StellaOps.Feedser.Core.Tests;
|
||||
|
||||
using FluentAssertions;
|
||||
using StellaOps.Feedser.Core;
|
||||
using Xunit;
|
||||
|
||||
public sealed class HunkSigExtractorTests
|
||||
{
|
||||
[Fact]
|
||||
public void ExtractFromDiff_SimpleAddition_ExtractsPatchSignature()
|
||||
{
|
||||
// Arrange
|
||||
var diff = @"--- a/src/file.c
|
||||
+++ b/src/file.c
|
||||
@@ -10,3 +10,4 @@ function foo() {
|
||||
existing line 1
|
||||
existing line 2
|
||||
+new line added
|
||||
existing line 3";
|
||||
|
||||
// Act
|
||||
var result = HunkSigExtractor.ExtractFromDiff(
|
||||
"CVE-2024-1234",
|
||||
"https://github.com/example/repo",
|
||||
"abc123def",
|
||||
diff);
|
||||
|
||||
// Assert
|
||||
result.CveId.Should().Be("CVE-2024-1234");
|
||||
result.UpstreamRepo.Should().Be("https://github.com/example/repo");
|
||||
result.CommitSha.Should().Be("abc123def");
|
||||
result.Hunks.Should().HaveCount(1);
|
||||
result.AffectedFiles.Should().ContainSingle("src/file.c");
|
||||
result.HunkHash.Should().NotBeNullOrEmpty();
|
||||
result.PatchSigId.Should().StartWith("sha256:");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ExtractFromDiff_MultipleHunks_ExtractsAllHunks()
|
||||
{
|
||||
// Arrange
|
||||
var diff = @"--- a/src/file1.c
|
||||
+++ b/src/file1.c
|
||||
@@ -10,3 +10,4 @@ function foo() {
|
||||
context line
|
||||
+added line 1
|
||||
context line
|
||||
--- a/src/file2.c
|
||||
+++ b/src/file2.c
|
||||
@@ -20,3 +20,4 @@ function bar() {
|
||||
context line
|
||||
+added line 2
|
||||
context line";
|
||||
|
||||
// Act
|
||||
var result = HunkSigExtractor.ExtractFromDiff(
|
||||
"CVE-2024-5678",
|
||||
"https://github.com/example/repo",
|
||||
"def456ghi",
|
||||
diff);
|
||||
|
||||
// Assert
|
||||
result.Hunks.Should().HaveCount(2);
|
||||
result.AffectedFiles.Should().HaveCount(2);
|
||||
result.AffectedFiles.Should().Contain("src/file1.c");
|
||||
result.AffectedFiles.Should().Contain("src/file2.c");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ExtractFromDiff_Removal_ExtractsRemovedLines()
|
||||
{
|
||||
// Arrange
|
||||
var diff = @"--- a/src/vuln.c
|
||||
+++ b/src/vuln.c
|
||||
@@ -15,5 +15,4 @@ function vulnerable() {
|
||||
safe line 1
|
||||
-unsafe line to remove
|
||||
safe line 2";
|
||||
|
||||
// Act
|
||||
var result = HunkSigExtractor.ExtractFromDiff(
|
||||
"CVE-2024-9999",
|
||||
"https://github.com/example/repo",
|
||||
"xyz789",
|
||||
diff);
|
||||
|
||||
// Assert
|
||||
result.Hunks.Should().HaveCount(1);
|
||||
var hunk = result.Hunks[0];
|
||||
hunk.RemovedLines.Should().HaveCount(1);
|
||||
hunk.AddedLines.Should().BeEmpty();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ExtractFromDiff_NormalizesWhitespace()
|
||||
{
|
||||
// Arrange
|
||||
var diff1 = @"--- a/test.c
|
||||
+++ b/test.c
|
||||
@@ -1,3 +1,4 @@
|
||||
context
|
||||
+ int x = 5; // added
|
||||
context";
|
||||
|
||||
var diff2 = @"--- a/test.c
|
||||
+++ b/test.c
|
||||
@@ -1,3 +1,4 @@
|
||||
context
|
||||
+int x=5;//added
|
||||
context";
|
||||
|
||||
// Act
|
||||
var result1 = HunkSigExtractor.ExtractFromDiff("CVE-1", "repo", "sha1", diff1);
|
||||
var result2 = HunkSigExtractor.ExtractFromDiff("CVE-1", "repo", "sha2", diff2);
|
||||
|
||||
// Assert
|
||||
// After normalization (whitespace removal, comment removal), hunk hashes should be similar
|
||||
result1.Hunks[0].HunkHash.Should().NotBeEmpty();
|
||||
result2.Hunks[0].HunkHash.Should().NotBeEmpty();
|
||||
// Note: Exact match depends on normalization strategy
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ExtractFromDiff_EmptyDiff_ReturnsNoHunks()
|
||||
{
|
||||
// Arrange
|
||||
var diff = "";
|
||||
|
||||
// Act
|
||||
var result = HunkSigExtractor.ExtractFromDiff(
|
||||
"CVE-2024-0000",
|
||||
"repo",
|
||||
"sha",
|
||||
diff);
|
||||
|
||||
// Assert
|
||||
result.Hunks.Should().BeEmpty();
|
||||
result.AffectedFiles.Should().BeEmpty();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ExtractFromDiff_MultipleChangesInOneHunk_CombinesCorrectly()
|
||||
{
|
||||
// Arrange
|
||||
var diff = @"--- a/src/complex.c
|
||||
+++ b/src/complex.c
|
||||
@@ -10,7 +10,8 @@ function complex() {
|
||||
context1
|
||||
context2
|
||||
-old line 1
|
||||
-old line 2
|
||||
+new line 1
|
||||
+new line 2
|
||||
+extra new line
|
||||
context3
|
||||
context4";
|
||||
|
||||
// Act
|
||||
var result = HunkSigExtractor.ExtractFromDiff(
|
||||
"CVE-2024-COMP",
|
||||
"https://example.com/repo",
|
||||
"complex123",
|
||||
diff);
|
||||
|
||||
// Assert
|
||||
result.Hunks.Should().HaveCount(1);
|
||||
var hunk = result.Hunks[0];
|
||||
hunk.AddedLines.Should().HaveCount(3);
|
||||
hunk.RemovedLines.Should().HaveCount(2);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ExtractFromDiff_DeterministicHashing_ProducesSameHashForSameContent()
|
||||
{
|
||||
// Arrange
|
||||
var diff = @"--- a/file.c
|
||||
+++ b/file.c
|
||||
@@ -1,2 +1,3 @@
|
||||
line1
|
||||
+new line
|
||||
line2";
|
||||
|
||||
// Act
|
||||
var result1 = HunkSigExtractor.ExtractFromDiff("CVE-1", "repo", "sha1", diff);
|
||||
var result2 = HunkSigExtractor.ExtractFromDiff("CVE-1", "repo", "sha1", diff);
|
||||
|
||||
// Assert
|
||||
result1.HunkHash.Should().Be(result2.HunkHash);
|
||||
result1.PatchSigId.Should().Be(result2.PatchSigId);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ExtractFromDiff_AffectedFiles_AreSortedAlphabetically()
|
||||
{
|
||||
// Arrange
|
||||
var diff = @"--- a/zzz.c
|
||||
+++ b/zzz.c
|
||||
@@ -1,1 +1,2 @@
|
||||
+added
|
||||
--- a/aaa.c
|
||||
+++ b/aaa.c
|
||||
@@ -1,1 +1,2 @@
|
||||
+added
|
||||
--- a/mmm.c
|
||||
+++ b/mmm.c
|
||||
@@ -1,1 +1,2 @@
|
||||
+added";
|
||||
|
||||
// Act
|
||||
var result = HunkSigExtractor.ExtractFromDiff("CVE-1", "repo", "sha", diff);
|
||||
|
||||
// Assert
|
||||
result.AffectedFiles.Should().Equal("aaa.c", "mmm.c", "zzz.c");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ExtractFromDiff_ExtractorVersion_IsRecorded()
|
||||
{
|
||||
// Arrange
|
||||
var diff = @"--- a/test.c
|
||||
+++ b/test.c
|
||||
@@ -1,1 +1,2 @@
|
||||
+line";
|
||||
|
||||
// Act
|
||||
var result = HunkSigExtractor.ExtractFromDiff("CVE-1", "repo", "sha", diff);
|
||||
|
||||
// Assert
|
||||
result.ExtractorVersion.Should().NotBeNullOrEmpty();
|
||||
result.ExtractorVersion.Should().MatchRegex(@"\d+\.\d+\.\d+");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ExtractFromDiff_ExtractedAt_IsRecent()
|
||||
{
|
||||
// Arrange
|
||||
var diff = @"--- a/test.c
|
||||
+++ b/test.c
|
||||
@@ -1,1 +1,2 @@
|
||||
+line";
|
||||
|
||||
// Act
|
||||
var before = DateTimeOffset.UtcNow.AddSeconds(-1);
|
||||
var result = HunkSigExtractor.ExtractFromDiff("CVE-1", "repo", "sha", diff);
|
||||
var after = DateTimeOffset.UtcNow.AddSeconds(1);
|
||||
|
||||
// Assert
|
||||
result.ExtractedAt.Should().BeAfter(before);
|
||||
result.ExtractedAt.Should().BeBefore(after);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ExtractFromDiff_ContextLines_ArePreserved()
|
||||
{
|
||||
// Arrange
|
||||
var diff = @"--- a/test.c
|
||||
+++ b/test.c
|
||||
@@ -5,5 +5,6 @@ function test() {
|
||||
context line 1
|
||||
context line 2
|
||||
+new line
|
||||
context line 3
|
||||
context line 4";
|
||||
|
||||
// Act
|
||||
var result = HunkSigExtractor.ExtractFromDiff("CVE-1", "repo", "sha", diff);
|
||||
|
||||
// Assert
|
||||
var hunk = result.Hunks[0];
|
||||
hunk.Context.Should().Contain("context line");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net10.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
<IsPackable>false</IsPackable>
|
||||
<IsTestProject>true</IsTestProject>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.11.1" />
|
||||
<PackageReference Include="xunit" Version="2.9.2" />
|
||||
<PackageReference Include="xunit.runner.visualstudio" Version="2.8.2">
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="FluentAssertions" Version="6.12.2" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\StellaOps.Feedser.Core\StellaOps.Feedser.Core.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
Reference in New Issue
Block a user