Fix build and code structure improvements. New but essential UI functionality. CI improvements. Documentation improvements. AI module improvements.
This commit is contained in:
@@ -8,6 +8,48 @@ Assemble deterministic SBOM artifacts (inventory, usage, BOM index) from analyze
|
||||
- Generate BOM index sidecars with roaring bitmap acceleration and usage flags.
|
||||
- Package artifacts with stable naming, hashing, and manifests for downstream storage and attestations.
|
||||
- Surface helper APIs for Scanner Worker/WebService to request compositions and exports.
|
||||
- **CBOM Support**: Generate CycloneDX 1.7 Cryptographic BOM with `cryptographicProperties` for crypto asset inventory.
|
||||
|
||||
## CBOM (Cryptographic BOM) Support
|
||||
|
||||
The Emit module supports CycloneDX 1.7 CBOM generation for cryptographic asset inventory:
|
||||
|
||||
### Key Components
|
||||
|
||||
| Component | Path | Purpose |
|
||||
|-----------|------|---------|
|
||||
| `ICryptoAssetExtractor` | `Cbom/ICryptoAssetExtractor.cs` | Interface for language-specific crypto extraction |
|
||||
| `CryptoProperties` | `Cbom/CryptoProperties.cs` | CycloneDX 1.7 crypto schema types |
|
||||
| `CbomAggregationService` | `Cbom/CbomAggregationService.cs` | Aggregates crypto assets with risk assessment |
|
||||
| `CycloneDxCbomWriter` | `Composition/CycloneDxCbomWriter.cs` | Injects cryptographicProperties into CycloneDX JSON |
|
||||
|
||||
### Crypto Extractors
|
||||
|
||||
Language-specific extractors implement `ICryptoAssetExtractor`:
|
||||
- `DotNetCryptoExtractor`: System.Security.Cryptography patterns
|
||||
- `JavaCryptoExtractor`: BouncyCastle, JWT libraries, JCA patterns
|
||||
- `NodeCryptoExtractor`: npm crypto packages (bcrypt, crypto-js, sodium, etc.)
|
||||
|
||||
### Usage Pattern
|
||||
|
||||
```csharp
|
||||
// 1. Aggregate crypto assets from components
|
||||
var cbomService = new CbomAggregationService(extractors, logger);
|
||||
var cbomResult = await cbomService.AggregateAsync(components, context);
|
||||
|
||||
// 2. Inject into CycloneDX output
|
||||
var enhancedJson = CycloneDxCbomWriter.InjectCbom(
|
||||
cycloneDxJson,
|
||||
cbomResult.ByComponent);
|
||||
```
|
||||
|
||||
### Risk Assessment
|
||||
|
||||
The aggregation service automatically assesses crypto risk:
|
||||
- **Deprecated**: MD5, SHA-1, DES, RC2, RC4
|
||||
- **Weak**: Small key sizes, ECB mode, unauthenticated encryption
|
||||
- **Quantum Vulnerable**: RSA, DSA, ECDSA, ECDH, DH
|
||||
- **Post-Quantum Ready**: ML-KEM, ML-DSA, SLH-DSA, SPHINCS+
|
||||
|
||||
## Interfaces & Dependencies
|
||||
- Consumes analyzer outputs (OS, language, native) and EntryTrace usage annotations.
|
||||
|
||||
@@ -0,0 +1,364 @@
|
||||
using System.Collections.Immutable;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using StellaOps.Scanner.Core.Contracts;
|
||||
|
||||
namespace StellaOps.Scanner.Emit.Cbom;
|
||||
|
||||
/// <summary>
|
||||
/// Service for aggregating crypto assets from all extractors into a unified CBOM.
|
||||
/// </summary>
|
||||
public interface ICbomAggregationService
|
||||
{
|
||||
/// <summary>
|
||||
/// Aggregates crypto assets from all components.
|
||||
/// </summary>
|
||||
Task<CbomAggregationResult> AggregateAsync(
|
||||
ImmutableArray<AggregatedComponent> components,
|
||||
CryptoAnalysisContext context,
|
||||
CancellationToken cancellationToken = default);
|
||||
|
||||
/// <summary>
|
||||
/// Computes risk assessment for crypto assets.
|
||||
/// </summary>
|
||||
CryptoRiskAssessment AssessRisk(ImmutableArray<CryptoAsset> assets);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Result of CBOM aggregation.
|
||||
/// </summary>
|
||||
public sealed record CbomAggregationResult
|
||||
{
|
||||
/// <summary>All discovered crypto assets.</summary>
|
||||
public required ImmutableArray<CryptoAsset> Assets { get; init; }
|
||||
|
||||
/// <summary>Assets grouped by component.</summary>
|
||||
public required ImmutableDictionary<string, ImmutableArray<CryptoAsset>> ByComponent { get; init; }
|
||||
|
||||
/// <summary>Unique algorithms discovered.</summary>
|
||||
public required ImmutableArray<string> UniqueAlgorithms { get; init; }
|
||||
|
||||
/// <summary>Risk assessment summary.</summary>
|
||||
public CryptoRiskAssessment? RiskAssessment { get; init; }
|
||||
|
||||
/// <summary>Timestamp of aggregation (UTC ISO 8601).</summary>
|
||||
public required string GeneratedAt { get; init; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Crypto risk assessment for the entire CBOM.
|
||||
/// </summary>
|
||||
public sealed record CryptoRiskAssessment
|
||||
{
|
||||
/// <summary>Overall risk score (0-100).</summary>
|
||||
public double OverallScore { get; init; }
|
||||
|
||||
/// <summary>Count of critical risk items.</summary>
|
||||
public int CriticalCount { get; init; }
|
||||
|
||||
/// <summary>Count of high risk items.</summary>
|
||||
public int HighCount { get; init; }
|
||||
|
||||
/// <summary>Count of medium risk items.</summary>
|
||||
public int MediumCount { get; init; }
|
||||
|
||||
/// <summary>Count of low risk items.</summary>
|
||||
public int LowCount { get; init; }
|
||||
|
||||
/// <summary>Deprecated algorithms found.</summary>
|
||||
public ImmutableArray<string> DeprecatedAlgorithms { get; init; } = ImmutableArray<string>.Empty;
|
||||
|
||||
/// <summary>Weak algorithms found.</summary>
|
||||
public ImmutableArray<string> WeakAlgorithms { get; init; } = ImmutableArray<string>.Empty;
|
||||
|
||||
/// <summary>Quantum-vulnerable algorithms found.</summary>
|
||||
public ImmutableArray<string> QuantumVulnerableAlgorithms { get; init; } = ImmutableArray<string>.Empty;
|
||||
|
||||
/// <summary>Post-quantum ready algorithms found.</summary>
|
||||
public ImmutableArray<string> PostQuantumAlgorithms { get; init; } = ImmutableArray<string>.Empty;
|
||||
|
||||
/// <summary>Migration recommendations.</summary>
|
||||
public ImmutableArray<CryptoMigrationRecommendation> MigrationRecommendations { get; init; } = ImmutableArray<CryptoMigrationRecommendation>.Empty;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Migration recommendation for crypto modernization.
|
||||
/// </summary>
|
||||
public sealed record CryptoMigrationRecommendation
|
||||
{
|
||||
/// <summary>Current algorithm/protocol.</summary>
|
||||
public required string From { get; init; }
|
||||
|
||||
/// <summary>Recommended replacement.</summary>
|
||||
public required string To { get; init; }
|
||||
|
||||
/// <summary>Priority level.</summary>
|
||||
public required CryptoRiskSeverity Priority { get; init; }
|
||||
|
||||
/// <summary>Reason for migration.</summary>
|
||||
public required string Reason { get; init; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Default implementation of CBOM aggregation service.
|
||||
/// </summary>
|
||||
public sealed class CbomAggregationService : ICbomAggregationService
|
||||
{
|
||||
private readonly IEnumerable<ICryptoAssetExtractor> _extractors;
|
||||
private readonly ILogger<CbomAggregationService> _logger;
|
||||
|
||||
public CbomAggregationService(
|
||||
IEnumerable<ICryptoAssetExtractor> extractors,
|
||||
ILogger<CbomAggregationService> logger)
|
||||
{
|
||||
_extractors = extractors;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
public async Task<CbomAggregationResult> AggregateAsync(
|
||||
ImmutableArray<AggregatedComponent> components,
|
||||
CryptoAnalysisContext context,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
var allAssets = new List<CryptoAsset>();
|
||||
var byComponent = new Dictionary<string, List<CryptoAsset>>();
|
||||
|
||||
foreach (var component in components)
|
||||
{
|
||||
var componentAssets = new List<CryptoAsset>();
|
||||
|
||||
foreach (var extractor in _extractors)
|
||||
{
|
||||
try
|
||||
{
|
||||
var assets = await extractor.ExtractAsync(component, context, cancellationToken);
|
||||
componentAssets.AddRange(assets);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogWarning(ex, "Crypto extraction failed for {Component} using {Extractor}",
|
||||
component.Identity.Key, extractor.GetType().Name);
|
||||
}
|
||||
}
|
||||
|
||||
if (componentAssets.Count > 0)
|
||||
{
|
||||
allAssets.AddRange(componentAssets);
|
||||
byComponent[component.Identity.Key] = componentAssets;
|
||||
}
|
||||
}
|
||||
|
||||
var assetsArray = allAssets.ToImmutableArray();
|
||||
var uniqueAlgorithms = assetsArray
|
||||
.Where(a => !string.IsNullOrEmpty(a.AlgorithmName))
|
||||
.Select(a => a.AlgorithmName!)
|
||||
.Distinct(StringComparer.OrdinalIgnoreCase)
|
||||
.OrderBy(s => s, StringComparer.Ordinal)
|
||||
.ToImmutableArray();
|
||||
|
||||
var byComponentImmutable = byComponent
|
||||
.ToImmutableDictionary(
|
||||
kv => kv.Key,
|
||||
kv => kv.Value.ToImmutableArray(),
|
||||
StringComparer.Ordinal);
|
||||
|
||||
return new CbomAggregationResult
|
||||
{
|
||||
Assets = assetsArray,
|
||||
ByComponent = byComponentImmutable,
|
||||
UniqueAlgorithms = uniqueAlgorithms,
|
||||
RiskAssessment = AssessRisk(assetsArray),
|
||||
GeneratedAt = DateTimeOffset.UtcNow.ToString("o")
|
||||
};
|
||||
}
|
||||
|
||||
public CryptoRiskAssessment AssessRisk(ImmutableArray<CryptoAsset> assets)
|
||||
{
|
||||
var deprecated = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
|
||||
var weak = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
|
||||
var quantumVulnerable = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
|
||||
var postQuantum = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
|
||||
var recommendations = new List<CryptoMigrationRecommendation>();
|
||||
|
||||
int criticalCount = 0, highCount = 0, mediumCount = 0, lowCount = 0;
|
||||
|
||||
foreach (var asset in assets)
|
||||
{
|
||||
var alg = asset.AlgorithmName?.ToUpperInvariant() ?? string.Empty;
|
||||
|
||||
// Check for deprecated algorithms
|
||||
if (IsDeprecatedAlgorithm(alg))
|
||||
{
|
||||
deprecated.Add(asset.AlgorithmName ?? alg);
|
||||
criticalCount++;
|
||||
AddMigrationRecommendation(recommendations, asset.AlgorithmName ?? alg, CryptoRiskSeverity.Critical);
|
||||
}
|
||||
// Check for weak algorithms
|
||||
else if (IsWeakAlgorithm(alg, asset.KeySizeBits))
|
||||
{
|
||||
weak.Add(asset.AlgorithmName ?? alg);
|
||||
highCount++;
|
||||
AddMigrationRecommendation(recommendations, asset.AlgorithmName ?? alg, CryptoRiskSeverity.High);
|
||||
}
|
||||
// Check quantum vulnerability
|
||||
else if (IsQuantumVulnerable(alg, asset.Primitive))
|
||||
{
|
||||
quantumVulnerable.Add(asset.AlgorithmName ?? alg);
|
||||
mediumCount++;
|
||||
}
|
||||
// Check post-quantum readiness
|
||||
else if (IsPostQuantumReady(alg, asset.Primitive))
|
||||
{
|
||||
postQuantum.Add(asset.AlgorithmName ?? alg);
|
||||
}
|
||||
else
|
||||
{
|
||||
lowCount++;
|
||||
}
|
||||
|
||||
// Count existing risk flags
|
||||
foreach (var flag in asset.RiskFlags)
|
||||
{
|
||||
switch (flag.Severity)
|
||||
{
|
||||
case CryptoRiskSeverity.Critical: criticalCount++; break;
|
||||
case CryptoRiskSeverity.High: highCount++; break;
|
||||
case CryptoRiskSeverity.Medium: mediumCount++; break;
|
||||
case CryptoRiskSeverity.Low: lowCount++; break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Compute overall score (0 = best, 100 = worst)
|
||||
double overallScore = 0;
|
||||
overallScore += criticalCount * 25;
|
||||
overallScore += highCount * 10;
|
||||
overallScore += mediumCount * 5;
|
||||
overallScore += lowCount * 1;
|
||||
overallScore = Math.Min(100, overallScore);
|
||||
|
||||
return new CryptoRiskAssessment
|
||||
{
|
||||
OverallScore = overallScore,
|
||||
CriticalCount = criticalCount,
|
||||
HighCount = highCount,
|
||||
MediumCount = mediumCount,
|
||||
LowCount = lowCount,
|
||||
DeprecatedAlgorithms = deprecated.Order().ToImmutableArray(),
|
||||
WeakAlgorithms = weak.Order().ToImmutableArray(),
|
||||
QuantumVulnerableAlgorithms = quantumVulnerable.Order().ToImmutableArray(),
|
||||
PostQuantumAlgorithms = postQuantum.Order().ToImmutableArray(),
|
||||
MigrationRecommendations = recommendations.ToImmutableArray()
|
||||
};
|
||||
}
|
||||
|
||||
private static bool IsDeprecatedAlgorithm(string alg)
|
||||
{
|
||||
return alg switch
|
||||
{
|
||||
"MD5" or "MD4" or "MD2" => true,
|
||||
"SHA1" or "SHA-1" => true,
|
||||
"DES" or "3DES" or "TRIPLEDES" or "TRIPLE-DES" => true,
|
||||
"RC2" or "RC4" => true,
|
||||
"BLOWFISH" => true,
|
||||
_ when alg.Contains("MD5") => true,
|
||||
_ when alg.Contains("SHA1") || alg.Contains("SHA-1") => true,
|
||||
_ when alg.Contains("DES") && !alg.Contains("AES") => true,
|
||||
_ => false
|
||||
};
|
||||
}
|
||||
|
||||
private static bool IsWeakAlgorithm(string alg, int? keySize)
|
||||
{
|
||||
// RSA with key size < 2048
|
||||
if ((alg.Contains("RSA") || alg == "RSA") && keySize.HasValue && keySize.Value < 2048)
|
||||
return true;
|
||||
|
||||
// AES with key size < 128
|
||||
if ((alg.Contains("AES") || alg == "AES") && keySize.HasValue && keySize.Value < 128)
|
||||
return true;
|
||||
|
||||
// ECDSA/ECDH with curve < 256 bits
|
||||
if ((alg.Contains("ECD") || alg.Contains("EC-")) && keySize.HasValue && keySize.Value < 256)
|
||||
return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private static bool IsQuantumVulnerable(string alg, CryptoPrimitive? primitive)
|
||||
{
|
||||
// All asymmetric algorithms based on factoring or discrete log are quantum-vulnerable
|
||||
if (primitive is CryptoPrimitive.Rsa or CryptoPrimitive.Dlog or CryptoPrimitive.Ec)
|
||||
return true;
|
||||
|
||||
return alg switch
|
||||
{
|
||||
"RSA" or "DSA" or "ECDSA" or "ECDH" or "DH" or "ECDHE" or "DHE" => true,
|
||||
_ when alg.Contains("RSA") => true,
|
||||
_ when alg.Contains("ECDSA") || alg.Contains("ECDH") => true,
|
||||
_ when alg.Contains("DSA") && !alg.Contains("ML-DSA") => true,
|
||||
_ => false
|
||||
};
|
||||
}
|
||||
|
||||
private static bool IsPostQuantumReady(string alg, CryptoPrimitive? primitive)
|
||||
{
|
||||
if (primitive is CryptoPrimitive.Lattice)
|
||||
return true;
|
||||
|
||||
// NIST post-quantum standards
|
||||
return alg switch
|
||||
{
|
||||
"ML-KEM" or "ML-DSA" or "SLH-DSA" or "FALCON" => true,
|
||||
"KYBER" or "DILITHIUM" or "SPHINCS+" => true,
|
||||
_ when alg.Contains("ML-KEM") || alg.Contains("ML-DSA") => true,
|
||||
_ when alg.Contains("KYBER") || alg.Contains("DILITHIUM") => true,
|
||||
_ => false
|
||||
};
|
||||
}
|
||||
|
||||
private static void AddMigrationRecommendation(
|
||||
List<CryptoMigrationRecommendation> recommendations,
|
||||
string fromAlg,
|
||||
CryptoRiskSeverity severity)
|
||||
{
|
||||
var algUpper = fromAlg.ToUpperInvariant();
|
||||
string? toAlg = null;
|
||||
string? reason = null;
|
||||
|
||||
if (algUpper.Contains("MD5") || algUpper.Contains("SHA1") || algUpper.Contains("SHA-1"))
|
||||
{
|
||||
toAlg = "SHA-256 or SHA-3";
|
||||
reason = "Algorithm is cryptographically broken and should not be used for security purposes";
|
||||
}
|
||||
else if (algUpper.Contains("DES") && !algUpper.Contains("AES"))
|
||||
{
|
||||
toAlg = "AES-256-GCM";
|
||||
reason = "DES and 3DES are deprecated due to small key/block size";
|
||||
}
|
||||
else if (algUpper.Contains("RC2") || algUpper.Contains("RC4"))
|
||||
{
|
||||
toAlg = "AES-256-GCM or ChaCha20-Poly1305";
|
||||
reason = "RC2/RC4 are deprecated due to known weaknesses";
|
||||
}
|
||||
else if (algUpper.Contains("RSA") && !algUpper.Contains("2048") && !algUpper.Contains("4096"))
|
||||
{
|
||||
toAlg = "RSA-2048+ or ECDSA P-256+";
|
||||
reason = "RSA key size should be at least 2048 bits";
|
||||
}
|
||||
|
||||
if (toAlg != null && reason != null)
|
||||
{
|
||||
// Avoid duplicates
|
||||
if (!recommendations.Any(r => r.From.Equals(fromAlg, StringComparison.OrdinalIgnoreCase)))
|
||||
{
|
||||
recommendations.Add(new CryptoMigrationRecommendation
|
||||
{
|
||||
From = fromAlg,
|
||||
To = toAlg,
|
||||
Priority = severity,
|
||||
Reason = reason
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,373 @@
|
||||
using System.Collections.Immutable;
|
||||
using System.Text.Json;
|
||||
using System.Text.Json.Nodes;
|
||||
|
||||
namespace StellaOps.Scanner.Emit.Cbom;
|
||||
|
||||
/// <summary>
|
||||
/// Serializer for CycloneDX 1.7 CBOM (Cryptographic Bill of Materials) extension.
|
||||
/// Injects cryptographicProperties into CycloneDX JSON output.
|
||||
/// </summary>
|
||||
public static class CbomSerializer
|
||||
{
|
||||
private static readonly JsonSerializerOptions SerializerOptions = new()
|
||||
{
|
||||
PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
|
||||
DefaultIgnoreCondition = System.Text.Json.Serialization.JsonIgnoreCondition.WhenWritingNull,
|
||||
WriteIndented = false
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// Injects CBOM cryptographicProperties into a CycloneDX 1.7 JSON string.
|
||||
/// </summary>
|
||||
/// <param name="cycloneDxJson">The CycloneDX JSON (v1.7).</param>
|
||||
/// <param name="cbomResult">The CBOM aggregation result with crypto assets by component.</param>
|
||||
/// <returns>Enhanced CycloneDX JSON with cryptographicProperties.</returns>
|
||||
public static string InjectCbom(string cycloneDxJson, CbomAggregationResult cbomResult)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(cycloneDxJson);
|
||||
ArgumentNullException.ThrowIfNull(cbomResult);
|
||||
|
||||
if (cbomResult.Assets.IsDefaultOrEmpty || cbomResult.Assets.Length == 0)
|
||||
{
|
||||
return cycloneDxJson;
|
||||
}
|
||||
|
||||
var root = JsonNode.Parse(cycloneDxJson);
|
||||
if (root is not JsonObject rootObj)
|
||||
{
|
||||
return cycloneDxJson;
|
||||
}
|
||||
|
||||
var components = rootObj["components"] as JsonArray;
|
||||
if (components is null || components.Count == 0)
|
||||
{
|
||||
return cycloneDxJson;
|
||||
}
|
||||
|
||||
// Index crypto assets by component key (bom-ref)
|
||||
var assetsByComponent = cbomResult.ByComponent;
|
||||
|
||||
foreach (var componentNode in components)
|
||||
{
|
||||
if (componentNode is not JsonObject componentObj)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var bomRef = componentObj["bom-ref"]?.GetValue<string>();
|
||||
if (string.IsNullOrEmpty(bomRef))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!assetsByComponent.TryGetValue(bomRef, out var cryptoAssets) || cryptoAssets.IsDefaultOrEmpty)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
// Convert crypto assets to CycloneDX cryptographicProperties format
|
||||
var cryptoPropsArray = new JsonArray();
|
||||
foreach (var asset in cryptoAssets)
|
||||
{
|
||||
var cryptoProp = SerializeCryptoAsset(asset);
|
||||
if (cryptoProp is not null)
|
||||
{
|
||||
cryptoPropsArray.Add(cryptoProp);
|
||||
}
|
||||
}
|
||||
|
||||
if (cryptoPropsArray.Count > 0)
|
||||
{
|
||||
componentObj["cryptographicProperties"] = cryptoPropsArray;
|
||||
}
|
||||
}
|
||||
|
||||
// Add CBOM metadata properties
|
||||
AddCbomMetadata(rootObj, cbomResult);
|
||||
|
||||
return rootObj.ToJsonString(SerializerOptions);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Serializes a CryptoAsset to CycloneDX 1.7 cryptographicProperties format.
|
||||
/// </summary>
|
||||
private static JsonObject? SerializeCryptoAsset(CryptoAsset asset)
|
||||
{
|
||||
var cryptoProp = new JsonObject
|
||||
{
|
||||
["assetType"] = asset.AssetType.ToString().ToLowerInvariant()
|
||||
};
|
||||
|
||||
// Add OID if available
|
||||
if (!string.IsNullOrEmpty(asset.Oid))
|
||||
{
|
||||
cryptoProp["oid"] = asset.Oid;
|
||||
}
|
||||
|
||||
// Serialize based on asset type
|
||||
switch (asset.AssetType)
|
||||
{
|
||||
case CryptoAssetType.Algorithm:
|
||||
cryptoProp["algorithmProperties"] = SerializeAlgorithmProperties(asset);
|
||||
break;
|
||||
|
||||
case CryptoAssetType.Certificate:
|
||||
if (!string.IsNullOrEmpty(asset.CertificateSubject) || !string.IsNullOrEmpty(asset.CertificateIssuer))
|
||||
{
|
||||
cryptoProp["certificateProperties"] = SerializeCertificateProperties(asset);
|
||||
}
|
||||
break;
|
||||
|
||||
case CryptoAssetType.Protocol:
|
||||
if (!string.IsNullOrEmpty(asset.ProtocolName))
|
||||
{
|
||||
cryptoProp["protocolProperties"] = SerializeProtocolProperties(asset);
|
||||
}
|
||||
break;
|
||||
|
||||
case CryptoAssetType.RelatedCryptoMaterial:
|
||||
cryptoProp["relatedCryptoMaterialProperties"] = SerializeRelatedCryptoMaterialProperties(asset);
|
||||
break;
|
||||
}
|
||||
|
||||
return cryptoProp;
|
||||
}
|
||||
|
||||
private static JsonObject SerializeAlgorithmProperties(CryptoAsset asset)
|
||||
{
|
||||
var props = new JsonObject();
|
||||
|
||||
if (asset.Primitive.HasValue)
|
||||
{
|
||||
props["primitive"] = asset.Primitive.Value.ToString().ToLowerInvariant();
|
||||
}
|
||||
|
||||
if (asset.Mode.HasValue)
|
||||
{
|
||||
props["mode"] = asset.Mode.Value.ToString().ToLowerInvariant();
|
||||
}
|
||||
|
||||
if (asset.Padding.HasValue)
|
||||
{
|
||||
props["padding"] = asset.Padding.Value.ToString().ToLowerInvariant();
|
||||
}
|
||||
|
||||
if (!asset.Functions.IsDefaultOrEmpty && asset.Functions.Length > 0)
|
||||
{
|
||||
var funcsArray = new JsonArray();
|
||||
foreach (var func in asset.Functions)
|
||||
{
|
||||
funcsArray.Add(func.ToString().ToLowerInvariant());
|
||||
}
|
||||
props["cryptoFunctions"] = funcsArray;
|
||||
}
|
||||
|
||||
if (asset.KeySizeBits.HasValue)
|
||||
{
|
||||
props["parameterSetIdentifier"] = $"{asset.KeySizeBits.Value}-bit";
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(asset.Curve))
|
||||
{
|
||||
props["curve"] = asset.Curve;
|
||||
}
|
||||
|
||||
if (asset.ExecutionEnvironment.HasValue)
|
||||
{
|
||||
props["executionEnvironment"] = asset.ExecutionEnvironment.Value.ToString().ToLowerInvariant();
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(asset.ImplementationPlatform))
|
||||
{
|
||||
props["implementationPlatform"] = asset.ImplementationPlatform;
|
||||
}
|
||||
|
||||
if (asset.NistQuantumSecurityLevel.HasValue)
|
||||
{
|
||||
props["nistQuantumSecurityLevel"] = asset.NistQuantumSecurityLevel.Value;
|
||||
}
|
||||
|
||||
if (asset.ClassicalSecurityLevel.HasValue)
|
||||
{
|
||||
props["classicalSecurityLevel"] = asset.ClassicalSecurityLevel.Value;
|
||||
}
|
||||
|
||||
return props;
|
||||
}
|
||||
|
||||
private static JsonObject SerializeCertificateProperties(CryptoAsset asset)
|
||||
{
|
||||
var props = new JsonObject();
|
||||
|
||||
if (!string.IsNullOrEmpty(asset.CertificateSubject))
|
||||
{
|
||||
props["subjectName"] = asset.CertificateSubject;
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(asset.CertificateIssuer))
|
||||
{
|
||||
props["issuerName"] = asset.CertificateIssuer;
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(asset.CertificateNotBefore))
|
||||
{
|
||||
props["notValidBefore"] = asset.CertificateNotBefore;
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(asset.CertificateNotAfter))
|
||||
{
|
||||
props["notValidAfter"] = asset.CertificateNotAfter;
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(asset.SignatureAlgorithmRef))
|
||||
{
|
||||
props["signatureAlgorithmRef"] = asset.SignatureAlgorithmRef;
|
||||
}
|
||||
|
||||
if (asset.CertificateFormat.HasValue)
|
||||
{
|
||||
props["certificateFormat"] = asset.CertificateFormat.Value.ToString().ToLowerInvariant();
|
||||
}
|
||||
|
||||
return props;
|
||||
}
|
||||
|
||||
private static JsonObject SerializeProtocolProperties(CryptoAsset asset)
|
||||
{
|
||||
var props = new JsonObject();
|
||||
|
||||
if (!string.IsNullOrEmpty(asset.ProtocolName))
|
||||
{
|
||||
props["type"] = asset.ProtocolName.ToLowerInvariant();
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(asset.ProtocolVersion))
|
||||
{
|
||||
props["version"] = asset.ProtocolVersion;
|
||||
}
|
||||
|
||||
if (!asset.CipherSuites.IsDefaultOrEmpty && asset.CipherSuites.Length > 0)
|
||||
{
|
||||
var suitesArray = new JsonArray();
|
||||
foreach (var suite in asset.CipherSuites)
|
||||
{
|
||||
suitesArray.Add(new JsonObject { ["name"] = suite });
|
||||
}
|
||||
props["cipherSuites"] = suitesArray;
|
||||
}
|
||||
|
||||
return props;
|
||||
}
|
||||
|
||||
private static JsonObject SerializeRelatedCryptoMaterialProperties(CryptoAsset asset)
|
||||
{
|
||||
var props = new JsonObject();
|
||||
|
||||
if (asset.MaterialType.HasValue)
|
||||
{
|
||||
props["type"] = asset.MaterialType.Value.ToString().ToLowerInvariant();
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(asset.MaterialId))
|
||||
{
|
||||
props["id"] = asset.MaterialId;
|
||||
}
|
||||
|
||||
if (asset.MaterialState.HasValue)
|
||||
{
|
||||
props["state"] = asset.MaterialState.Value.ToString().ToLowerInvariant();
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(asset.AlgorithmRef))
|
||||
{
|
||||
props["algorithmRef"] = asset.AlgorithmRef;
|
||||
}
|
||||
|
||||
if (asset.KeySizeBits.HasValue)
|
||||
{
|
||||
props["size"] = asset.KeySizeBits.Value;
|
||||
}
|
||||
|
||||
return props;
|
||||
}
|
||||
|
||||
private static void AddCbomMetadata(JsonObject rootObj, CbomAggregationResult cbomResult)
|
||||
{
|
||||
var metadata = rootObj["metadata"] as JsonObject;
|
||||
if (metadata is null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var properties = metadata["properties"] as JsonArray ?? new JsonArray();
|
||||
|
||||
// Add CBOM summary properties
|
||||
properties.Add(new JsonObject
|
||||
{
|
||||
["name"] = "stellaops:cbom.generatedAt",
|
||||
["value"] = cbomResult.GeneratedAt
|
||||
});
|
||||
|
||||
properties.Add(new JsonObject
|
||||
{
|
||||
["name"] = "stellaops:cbom.totalAssets",
|
||||
["value"] = cbomResult.Assets.Length.ToString()
|
||||
});
|
||||
|
||||
properties.Add(new JsonObject
|
||||
{
|
||||
["name"] = "stellaops:cbom.uniqueAlgorithms",
|
||||
["value"] = cbomResult.UniqueAlgorithms.Length.ToString()
|
||||
});
|
||||
|
||||
if (cbomResult.RiskAssessment is not null)
|
||||
{
|
||||
var risk = cbomResult.RiskAssessment;
|
||||
|
||||
properties.Add(new JsonObject
|
||||
{
|
||||
["name"] = "stellaops:cbom.riskScore",
|
||||
["value"] = risk.OverallScore.ToString("F2", System.Globalization.CultureInfo.InvariantCulture)
|
||||
});
|
||||
|
||||
if (risk.DeprecatedAlgorithms.Length > 0)
|
||||
{
|
||||
properties.Add(new JsonObject
|
||||
{
|
||||
["name"] = "stellaops:cbom.deprecatedAlgorithms",
|
||||
["value"] = string.Join(",", risk.DeprecatedAlgorithms)
|
||||
});
|
||||
}
|
||||
|
||||
if (risk.WeakAlgorithms.Length > 0)
|
||||
{
|
||||
properties.Add(new JsonObject
|
||||
{
|
||||
["name"] = "stellaops:cbom.weakAlgorithms",
|
||||
["value"] = string.Join(",", risk.WeakAlgorithms)
|
||||
});
|
||||
}
|
||||
|
||||
if (risk.QuantumVulnerableAlgorithms.Length > 0)
|
||||
{
|
||||
properties.Add(new JsonObject
|
||||
{
|
||||
["name"] = "stellaops:cbom.quantumVulnerable",
|
||||
["value"] = string.Join(",", risk.QuantumVulnerableAlgorithms)
|
||||
});
|
||||
}
|
||||
|
||||
if (risk.PostQuantumAlgorithms.Length > 0)
|
||||
{
|
||||
properties.Add(new JsonObject
|
||||
{
|
||||
["name"] = "stellaops:cbom.postQuantumReady",
|
||||
["value"] = string.Join(",", risk.PostQuantumAlgorithms)
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
metadata["properties"] = properties;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,467 @@
|
||||
using System.Collections.Immutable;
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace StellaOps.Scanner.Emit.Cbom;
|
||||
|
||||
/// <summary>
|
||||
/// CycloneDX 1.7 Cryptographic Properties (CBOM).
|
||||
/// Per CycloneDX 1.7 specification for cryptographic asset inventory.
|
||||
/// </summary>
|
||||
public sealed record CryptoProperties
|
||||
{
|
||||
/// <summary>Type of cryptographic asset.</summary>
|
||||
[JsonPropertyName("assetType")]
|
||||
public required CryptoAssetType AssetType { get; init; }
|
||||
|
||||
/// <summary>Algorithm reference when asset type is Algorithm.</summary>
|
||||
[JsonPropertyName("algorithmProperties")]
|
||||
public AlgorithmProperties? AlgorithmProperties { get; init; }
|
||||
|
||||
/// <summary>Certificate reference when asset type is Certificate.</summary>
|
||||
[JsonPropertyName("certificateProperties")]
|
||||
public CertificateProperties? CertificateProperties { get; init; }
|
||||
|
||||
/// <summary>Protocol reference when asset type is Protocol.</summary>
|
||||
[JsonPropertyName("protocolProperties")]
|
||||
public ProtocolProperties? ProtocolProperties { get; init; }
|
||||
|
||||
/// <summary>Key properties when asset type is Key.</summary>
|
||||
[JsonPropertyName("relatedCryptoMaterialProperties")]
|
||||
public RelatedCryptoMaterialProperties? RelatedCryptoMaterialProperties { get; init; }
|
||||
|
||||
/// <summary>Object Identifier per IANA/ISO.</summary>
|
||||
[JsonPropertyName("oid")]
|
||||
public string? Oid { get; init; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// CycloneDX 1.7 Algorithm Properties.
|
||||
/// </summary>
|
||||
public sealed record AlgorithmProperties
|
||||
{
|
||||
/// <summary>Algorithm primitive (block-cipher, stream-cipher, hash, etc.).</summary>
|
||||
[JsonPropertyName("primitive")]
|
||||
public CryptoPrimitive? Primitive { get; init; }
|
||||
|
||||
/// <summary>Algorithm mode (CBC, GCM, CTR, etc.).</summary>
|
||||
[JsonPropertyName("mode")]
|
||||
public CryptoMode? Mode { get; init; }
|
||||
|
||||
/// <summary>Padding scheme (PKCS7, OAEP, etc.).</summary>
|
||||
[JsonPropertyName("padding")]
|
||||
public CryptoPadding? Padding { get; init; }
|
||||
|
||||
/// <summary>Cryptographic functions this algorithm performs.</summary>
|
||||
[JsonPropertyName("cryptoFunctions")]
|
||||
public ImmutableArray<CryptoFunction> CryptoFunctions { get; init; } = ImmutableArray<CryptoFunction>.Empty;
|
||||
|
||||
/// <summary>Key size in bits.</summary>
|
||||
[JsonPropertyName("parameterSetIdentifier")]
|
||||
public string? ParameterSetIdentifier { get; init; }
|
||||
|
||||
/// <summary>Elliptic curve identifier for EC algorithms.</summary>
|
||||
[JsonPropertyName("curve")]
|
||||
public string? Curve { get; init; }
|
||||
|
||||
/// <summary>Execution environment (software, hardware, HSM, TEE).</summary>
|
||||
[JsonPropertyName("executionEnvironment")]
|
||||
public ExecutionEnvironment? ExecutionEnvironment { get; init; }
|
||||
|
||||
/// <summary>Implementation platform (native, OpenSSL, BouncyCastle, etc.).</summary>
|
||||
[JsonPropertyName("implementationPlatform")]
|
||||
public string? ImplementationPlatform { get; init; }
|
||||
|
||||
/// <summary>NIST post-quantum security level (1-5).</summary>
|
||||
[JsonPropertyName("nistQuantumSecurityLevel")]
|
||||
public int? NistQuantumSecurityLevel { get; init; }
|
||||
|
||||
/// <summary>Classical security level equivalent in bits.</summary>
|
||||
[JsonPropertyName("classicalSecurityLevel")]
|
||||
public int? ClassicalSecurityLevel { get; init; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// CycloneDX 1.7 Certificate Properties.
|
||||
/// </summary>
|
||||
public sealed record CertificateProperties
|
||||
{
|
||||
/// <summary>Certificate subject distinguished name.</summary>
|
||||
[JsonPropertyName("subjectName")]
|
||||
public string? SubjectName { get; init; }
|
||||
|
||||
/// <summary>Certificate issuer distinguished name.</summary>
|
||||
[JsonPropertyName("issuerName")]
|
||||
public string? IssuerName { get; init; }
|
||||
|
||||
/// <summary>Certificate not valid before date (ISO 8601).</summary>
|
||||
[JsonPropertyName("notValidBefore")]
|
||||
public string? NotValidBefore { get; init; }
|
||||
|
||||
/// <summary>Certificate not valid after date (ISO 8601).</summary>
|
||||
[JsonPropertyName("notValidAfter")]
|
||||
public string? NotValidAfter { get; init; }
|
||||
|
||||
/// <summary>Signature algorithm OID.</summary>
|
||||
[JsonPropertyName("signatureAlgorithmRef")]
|
||||
public string? SignatureAlgorithmRef { get; init; }
|
||||
|
||||
/// <summary>Subject public key algorithm OID.</summary>
|
||||
[JsonPropertyName("subjectPublicKeyRef")]
|
||||
public string? SubjectPublicKeyRef { get; init; }
|
||||
|
||||
/// <summary>Certificate format (X.509, PGP, etc.).</summary>
|
||||
[JsonPropertyName("certificateFormat")]
|
||||
public CertificateFormat? CertificateFormat { get; init; }
|
||||
|
||||
/// <summary>Certificate extension key usages.</summary>
|
||||
[JsonPropertyName("certificateExtension")]
|
||||
public string? CertificateExtension { get; init; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// CycloneDX 1.7 Protocol Properties.
|
||||
/// </summary>
|
||||
public sealed record ProtocolProperties
|
||||
{
|
||||
/// <summary>Protocol type (TLS, SSH, IPsec, etc.).</summary>
|
||||
[JsonPropertyName("type")]
|
||||
public ProtocolType? Type { get; init; }
|
||||
|
||||
/// <summary>Protocol version (e.g., "1.3" for TLS 1.3).</summary>
|
||||
[JsonPropertyName("version")]
|
||||
public string? Version { get; init; }
|
||||
|
||||
/// <summary>Cipher suites supported.</summary>
|
||||
[JsonPropertyName("cipherSuites")]
|
||||
public ImmutableArray<CipherSuite> CipherSuites { get; init; } = ImmutableArray<CipherSuite>.Empty;
|
||||
|
||||
/// <summary>IKE version for IPsec.</summary>
|
||||
[JsonPropertyName("ikev2TransformTypes")]
|
||||
public IkeV2TransformTypes? IkeV2TransformTypes { get; init; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// CycloneDX 1.7 Related Crypto Material Properties.
|
||||
/// Describes keys, nonces, salts, IVs, etc.
|
||||
/// </summary>
|
||||
public sealed record RelatedCryptoMaterialProperties
|
||||
{
|
||||
/// <summary>Type of crypto material.</summary>
|
||||
[JsonPropertyName("type")]
|
||||
public RelatedCryptoMaterialType? Type { get; init; }
|
||||
|
||||
/// <summary>Material identifier/reference.</summary>
|
||||
[JsonPropertyName("id")]
|
||||
public string? Id { get; init; }
|
||||
|
||||
/// <summary>State of the crypto material.</summary>
|
||||
[JsonPropertyName("state")]
|
||||
public CryptoMaterialState? State { get; init; }
|
||||
|
||||
/// <summary>Algorithm reference this material is used with.</summary>
|
||||
[JsonPropertyName("algorithmRef")]
|
||||
public string? AlgorithmRef { get; init; }
|
||||
|
||||
/// <summary>Creation date (ISO 8601).</summary>
|
||||
[JsonPropertyName("creationDate")]
|
||||
public string? CreationDate { get; init; }
|
||||
|
||||
/// <summary>Activation date (ISO 8601).</summary>
|
||||
[JsonPropertyName("activationDate")]
|
||||
public string? ActivationDate { get; init; }
|
||||
|
||||
/// <summary>Expiration date (ISO 8601).</summary>
|
||||
[JsonPropertyName("expirationDate")]
|
||||
public string? ExpirationDate { get; init; }
|
||||
|
||||
/// <summary>Size in bits.</summary>
|
||||
[JsonPropertyName("size")]
|
||||
public int? Size { get; init; }
|
||||
|
||||
/// <summary>Format of the crypto material.</summary>
|
||||
[JsonPropertyName("format")]
|
||||
public string? Format { get; init; }
|
||||
|
||||
/// <summary>Secured by reference to another component.</summary>
|
||||
[JsonPropertyName("securedBy")]
|
||||
public SecuredBy? SecuredBy { get; init; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Cipher suite representation for protocols.
|
||||
/// </summary>
|
||||
public sealed record CipherSuite
|
||||
{
|
||||
/// <summary>IANA cipher suite name.</summary>
|
||||
[JsonPropertyName("name")]
|
||||
public string? Name { get; init; }
|
||||
|
||||
/// <summary>Algorithms used in this suite.</summary>
|
||||
[JsonPropertyName("algorithms")]
|
||||
public ImmutableArray<string> Algorithms { get; init; } = ImmutableArray<string>.Empty;
|
||||
|
||||
/// <summary>Identifiers (hex codes).</summary>
|
||||
[JsonPropertyName("identifiers")]
|
||||
public ImmutableArray<string> Identifiers { get; init; } = ImmutableArray<string>.Empty;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// IKEv2 transform types for IPsec.
|
||||
/// </summary>
|
||||
public sealed record IkeV2TransformTypes
|
||||
{
|
||||
[JsonPropertyName("encr")]
|
||||
public ImmutableArray<string> Encr { get; init; } = ImmutableArray<string>.Empty;
|
||||
|
||||
[JsonPropertyName("prf")]
|
||||
public ImmutableArray<string> Prf { get; init; } = ImmutableArray<string>.Empty;
|
||||
|
||||
[JsonPropertyName("integ")]
|
||||
public ImmutableArray<string> Integ { get; init; } = ImmutableArray<string>.Empty;
|
||||
|
||||
[JsonPropertyName("ke")]
|
||||
public ImmutableArray<string> Ke { get; init; } = ImmutableArray<string>.Empty;
|
||||
|
||||
[JsonPropertyName("esn")]
|
||||
public bool? Esn { get; init; }
|
||||
|
||||
[JsonPropertyName("auth")]
|
||||
public ImmutableArray<string> Auth { get; init; } = ImmutableArray<string>.Empty;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reference to security mechanism protecting crypto material.
|
||||
/// </summary>
|
||||
public sealed record SecuredBy
|
||||
{
|
||||
[JsonPropertyName("mechanism")]
|
||||
public string? Mechanism { get; init; }
|
||||
|
||||
[JsonPropertyName("algorithmRef")]
|
||||
public string? AlgorithmRef { get; init; }
|
||||
}
|
||||
|
||||
#region Enums
|
||||
|
||||
/// <summary>
|
||||
/// Type of cryptographic asset per CycloneDX 1.7.
|
||||
/// </summary>
|
||||
[JsonConverter(typeof(JsonStringEnumConverter))]
|
||||
public enum CryptoAssetType
|
||||
{
|
||||
/// <summary>Cryptographic algorithm.</summary>
|
||||
Algorithm,
|
||||
/// <summary>X.509 or other certificate.</summary>
|
||||
Certificate,
|
||||
/// <summary>Cryptographic protocol.</summary>
|
||||
Protocol,
|
||||
/// <summary>Related cryptographic material (keys, nonces, etc.).</summary>
|
||||
RelatedCryptoMaterial
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Cryptographic primitive types.
|
||||
/// </summary>
|
||||
[JsonConverter(typeof(JsonStringEnumConverter))]
|
||||
public enum CryptoPrimitive
|
||||
{
|
||||
/// <summary>Discrete logarithm (DH, DSA).</summary>
|
||||
Dlog,
|
||||
/// <summary>Elliptic curve cryptography.</summary>
|
||||
Ec,
|
||||
/// <summary>RSA family.</summary>
|
||||
Rsa,
|
||||
/// <summary>Lattice-based cryptography.</summary>
|
||||
Lattice,
|
||||
/// <summary>Hash-based cryptography.</summary>
|
||||
Hash,
|
||||
/// <summary>Block cipher.</summary>
|
||||
BlockCipher,
|
||||
/// <summary>Stream cipher.</summary>
|
||||
StreamCipher,
|
||||
/// <summary>Authenticated encryption with associated data.</summary>
|
||||
Aead,
|
||||
/// <summary>Message authentication code.</summary>
|
||||
Mac,
|
||||
/// <summary>Key derivation function.</summary>
|
||||
Kdf,
|
||||
/// <summary>Key encapsulation mechanism.</summary>
|
||||
Kem,
|
||||
/// <summary>Password-based key derivation.</summary>
|
||||
Pbkdf,
|
||||
/// <summary>Digital signature.</summary>
|
||||
Signature,
|
||||
/// <summary>Key agreement.</summary>
|
||||
KeyAgree,
|
||||
/// <summary>Pseudorandom number generator.</summary>
|
||||
Prng,
|
||||
/// <summary>Unknown or other.</summary>
|
||||
Other
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Block cipher modes of operation.
|
||||
/// </summary>
|
||||
[JsonConverter(typeof(JsonStringEnumConverter))]
|
||||
public enum CryptoMode
|
||||
{
|
||||
Cbc,
|
||||
Ecb,
|
||||
Ccm,
|
||||
Gcm,
|
||||
Cfb,
|
||||
Ofb,
|
||||
Ctr,
|
||||
Xts,
|
||||
Wrap,
|
||||
Other
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Padding schemes.
|
||||
/// </summary>
|
||||
[JsonConverter(typeof(JsonStringEnumConverter))]
|
||||
public enum CryptoPadding
|
||||
{
|
||||
Pkcs7,
|
||||
Oaep,
|
||||
Pkcs1v15,
|
||||
Pss,
|
||||
X923,
|
||||
Raw,
|
||||
None,
|
||||
Other
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Cryptographic functions an algorithm can perform.
|
||||
/// </summary>
|
||||
[JsonConverter(typeof(JsonStringEnumConverter))]
|
||||
public enum CryptoFunction
|
||||
{
|
||||
/// <summary>Random number generation.</summary>
|
||||
Generate,
|
||||
/// <summary>Key generation.</summary>
|
||||
Keygen,
|
||||
/// <summary>Key derivation.</summary>
|
||||
Derive,
|
||||
/// <summary>Digital signature creation.</summary>
|
||||
Sign,
|
||||
/// <summary>Signature verification.</summary>
|
||||
Verify,
|
||||
/// <summary>Encryption.</summary>
|
||||
Encrypt,
|
||||
/// <summary>Decryption.</summary>
|
||||
Decrypt,
|
||||
/// <summary>Authenticated encryption.</summary>
|
||||
Encapsulate,
|
||||
/// <summary>Authenticated decryption.</summary>
|
||||
Decapsulate,
|
||||
/// <summary>Hashing/digest.</summary>
|
||||
Digest,
|
||||
/// <summary>Message authentication.</summary>
|
||||
Tag,
|
||||
/// <summary>Key wrapping.</summary>
|
||||
KeyWrap,
|
||||
/// <summary>Key unwrapping.</summary>
|
||||
KeyUnwrap,
|
||||
/// <summary>Key agreement.</summary>
|
||||
KeyAgree,
|
||||
/// <summary>Other function.</summary>
|
||||
Other
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Execution environment for crypto operations.
|
||||
/// </summary>
|
||||
[JsonConverter(typeof(JsonStringEnumConverter))]
|
||||
public enum ExecutionEnvironment
|
||||
{
|
||||
/// <summary>Software implementation.</summary>
|
||||
Software,
|
||||
/// <summary>Hardware security module.</summary>
|
||||
HardwareSecurityModule,
|
||||
/// <summary>Trusted execution environment.</summary>
|
||||
TrustedExecutionEnvironment,
|
||||
/// <summary>Hardware accelerator.</summary>
|
||||
Hardware,
|
||||
/// <summary>Unknown environment.</summary>
|
||||
Unknown
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Certificate formats.
|
||||
/// </summary>
|
||||
[JsonConverter(typeof(JsonStringEnumConverter))]
|
||||
public enum CertificateFormat
|
||||
{
|
||||
X509,
|
||||
Pgp,
|
||||
Pkcs7,
|
||||
Other
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Cryptographic protocol types.
|
||||
/// </summary>
|
||||
[JsonConverter(typeof(JsonStringEnumConverter))]
|
||||
public enum ProtocolType
|
||||
{
|
||||
Tls,
|
||||
Ssh,
|
||||
Ipsec,
|
||||
Ike,
|
||||
Sstp,
|
||||
Wpa,
|
||||
Other
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Types of related cryptographic material.
|
||||
/// </summary>
|
||||
[JsonConverter(typeof(JsonStringEnumConverter))]
|
||||
public enum RelatedCryptoMaterialType
|
||||
{
|
||||
/// <summary>Private key.</summary>
|
||||
PrivateKey,
|
||||
/// <summary>Public key.</summary>
|
||||
PublicKey,
|
||||
/// <summary>Secret/symmetric key.</summary>
|
||||
SecretKey,
|
||||
/// <summary>Session key.</summary>
|
||||
Key,
|
||||
/// <summary>Cryptographic nonce.</summary>
|
||||
Nonce,
|
||||
/// <summary>Cryptographic seed.</summary>
|
||||
Seed,
|
||||
/// <summary>Initialization vector.</summary>
|
||||
Iv,
|
||||
/// <summary>Salt for key derivation.</summary>
|
||||
Salt,
|
||||
/// <summary>Shared secret (DH/ECDH).</summary>
|
||||
SharedSecret,
|
||||
/// <summary>Other material.</summary>
|
||||
Other
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// State of cryptographic material.
|
||||
/// </summary>
|
||||
[JsonConverter(typeof(JsonStringEnumConverter))]
|
||||
public enum CryptoMaterialState
|
||||
{
|
||||
/// <summary>Pre-activation.</summary>
|
||||
PreActivation,
|
||||
/// <summary>Active/in-use.</summary>
|
||||
Active,
|
||||
/// <summary>Suspended.</summary>
|
||||
Suspended,
|
||||
/// <summary>Deactivated.</summary>
|
||||
Deactivated,
|
||||
/// <summary>Compromised.</summary>
|
||||
Compromised,
|
||||
/// <summary>Destroyed.</summary>
|
||||
Destroyed
|
||||
}
|
||||
|
||||
#endregion
|
||||
@@ -0,0 +1,196 @@
|
||||
using System.Collections.Immutable;
|
||||
using StellaOps.Scanner.Core.Contracts;
|
||||
|
||||
namespace StellaOps.Scanner.Emit.Cbom;
|
||||
|
||||
/// <summary>
|
||||
/// Interface for crypto asset extractors that analyze components for cryptographic usage.
|
||||
/// Each language analyzer implements this to detect crypto patterns.
|
||||
/// </summary>
|
||||
public interface ICryptoAssetExtractor
|
||||
{
|
||||
/// <summary>
|
||||
/// Ecosystems this extractor supports.
|
||||
/// </summary>
|
||||
ImmutableArray<string> SupportedEcosystems { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Extracts cryptographic assets from a component.
|
||||
/// </summary>
|
||||
/// <param name="component">The component to analyze.</param>
|
||||
/// <param name="analysisContext">Analysis context with file access.</param>
|
||||
/// <param name="cancellationToken">Cancellation token.</param>
|
||||
/// <returns>Extracted crypto assets.</returns>
|
||||
Task<ImmutableArray<CryptoAsset>> ExtractAsync(
|
||||
AggregatedComponent component,
|
||||
CryptoAnalysisContext analysisContext,
|
||||
CancellationToken cancellationToken = default);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Context for crypto analysis providing access to component artifacts.
|
||||
/// </summary>
|
||||
public sealed record CryptoAnalysisContext
|
||||
{
|
||||
/// <summary>Layer digest for file access.</summary>
|
||||
public required string LayerDigest { get; init; }
|
||||
|
||||
/// <summary>File system path mappings.</summary>
|
||||
public ImmutableDictionary<string, string> FilePaths { get; init; } = ImmutableDictionary<string, string>.Empty;
|
||||
|
||||
/// <summary>Optional: Pre-analyzed metadata from language analyzer.</summary>
|
||||
public ImmutableDictionary<string, object> Metadata { get; init; } = ImmutableDictionary<string, object>.Empty;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Extracted cryptographic asset from analysis.
|
||||
/// </summary>
|
||||
public sealed record CryptoAsset
|
||||
{
|
||||
/// <summary>Unique identifier for this crypto asset.</summary>
|
||||
public required string Id { get; init; }
|
||||
|
||||
/// <summary>Component key this asset belongs to.</summary>
|
||||
public required string ComponentKey { get; init; }
|
||||
|
||||
/// <summary>Type of crypto asset.</summary>
|
||||
public required CryptoAssetType AssetType { get; init; }
|
||||
|
||||
/// <summary>Algorithm name (e.g., "AES-256-GCM", "RSA-2048", "SHA-256").</summary>
|
||||
public string? AlgorithmName { get; init; }
|
||||
|
||||
/// <summary>OID if available (e.g., "2.16.840.1.101.3.4.1.46" for AES-256-GCM).</summary>
|
||||
public string? Oid { get; init; }
|
||||
|
||||
/// <summary>Key size in bits if applicable.</summary>
|
||||
public int? KeySizeBits { get; init; }
|
||||
|
||||
/// <summary>Cryptographic primitive category.</summary>
|
||||
public CryptoPrimitive? Primitive { get; init; }
|
||||
|
||||
/// <summary>Functions this algorithm performs.</summary>
|
||||
public ImmutableArray<CryptoFunction> Functions { get; init; } = ImmutableArray<CryptoFunction>.Empty;
|
||||
|
||||
/// <summary>Mode of operation for block ciphers.</summary>
|
||||
public CryptoMode? Mode { get; init; }
|
||||
|
||||
/// <summary>Padding scheme if applicable.</summary>
|
||||
public CryptoPadding? Padding { get; init; }
|
||||
|
||||
/// <summary>Elliptic curve name for EC algorithms.</summary>
|
||||
public string? Curve { get; init; }
|
||||
|
||||
/// <summary>Execution environment.</summary>
|
||||
public ExecutionEnvironment? ExecutionEnvironment { get; init; }
|
||||
|
||||
/// <summary>Implementation library (e.g., "OpenSSL", "BouncyCastle", "System.Security.Cryptography").</summary>
|
||||
public string? ImplementationPlatform { get; init; }
|
||||
|
||||
/// <summary>Confidence of detection (0.0 - 1.0).</summary>
|
||||
public double Confidence { get; init; } = 1.0;
|
||||
|
||||
/// <summary>Source evidence (file path, method, etc.).</summary>
|
||||
public ImmutableArray<string> Evidence { get; init; } = ImmutableArray<string>.Empty;
|
||||
|
||||
/// <summary>Risk flags identified for this crypto asset.</summary>
|
||||
public ImmutableArray<CryptoRiskFlag> RiskFlags { get; init; } = ImmutableArray<CryptoRiskFlag>.Empty;
|
||||
|
||||
/// <summary>Full CycloneDX crypto properties for serialization.</summary>
|
||||
public CryptoProperties? CryptoProperties { get; init; }
|
||||
|
||||
#region Certificate Properties (for AssetType.Certificate)
|
||||
|
||||
/// <summary>Certificate subject distinguished name.</summary>
|
||||
public string? CertificateSubject { get; init; }
|
||||
|
||||
/// <summary>Certificate issuer distinguished name.</summary>
|
||||
public string? CertificateIssuer { get; init; }
|
||||
|
||||
/// <summary>Certificate not valid before (ISO 8601).</summary>
|
||||
public string? CertificateNotBefore { get; init; }
|
||||
|
||||
/// <summary>Certificate not valid after (ISO 8601).</summary>
|
||||
public string? CertificateNotAfter { get; init; }
|
||||
|
||||
/// <summary>Signature algorithm reference.</summary>
|
||||
public string? SignatureAlgorithmRef { get; init; }
|
||||
|
||||
/// <summary>Certificate format.</summary>
|
||||
public CertificateFormat? CertificateFormat { get; init; }
|
||||
|
||||
#endregion
|
||||
|
||||
#region Protocol Properties (for AssetType.Protocol)
|
||||
|
||||
/// <summary>Protocol name (TLS, SSH, etc.).</summary>
|
||||
public string? ProtocolName { get; init; }
|
||||
|
||||
/// <summary>Protocol version.</summary>
|
||||
public string? ProtocolVersion { get; init; }
|
||||
|
||||
/// <summary>Cipher suites supported.</summary>
|
||||
public ImmutableArray<string> CipherSuites { get; init; } = ImmutableArray<string>.Empty;
|
||||
|
||||
#endregion
|
||||
|
||||
#region Related Crypto Material Properties (for AssetType.RelatedCryptoMaterial)
|
||||
|
||||
/// <summary>Material type (key, nonce, salt, etc.).</summary>
|
||||
public RelatedCryptoMaterialType? MaterialType { get; init; }
|
||||
|
||||
/// <summary>Material identifier.</summary>
|
||||
public string? MaterialId { get; init; }
|
||||
|
||||
/// <summary>Material state.</summary>
|
||||
public CryptoMaterialState? MaterialState { get; init; }
|
||||
|
||||
/// <summary>Algorithm reference for this material.</summary>
|
||||
public string? AlgorithmRef { get; init; }
|
||||
|
||||
#endregion
|
||||
|
||||
#region Post-Quantum Properties
|
||||
|
||||
/// <summary>NIST post-quantum security level (1-5).</summary>
|
||||
public int? NistQuantumSecurityLevel { get; init; }
|
||||
|
||||
/// <summary>Classical security level equivalent in bits.</summary>
|
||||
public int? ClassicalSecurityLevel { get; init; }
|
||||
|
||||
#endregion
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Risk flags for cryptographic assets.
|
||||
/// </summary>
|
||||
public sealed record CryptoRiskFlag
|
||||
{
|
||||
/// <summary>Risk identifier.</summary>
|
||||
public required string RiskId { get; init; }
|
||||
|
||||
/// <summary>Risk severity (Low, Medium, High, Critical).</summary>
|
||||
public required CryptoRiskSeverity Severity { get; init; }
|
||||
|
||||
/// <summary>Human-readable description.</summary>
|
||||
public required string Description { get; init; }
|
||||
|
||||
/// <summary>Recommended action.</summary>
|
||||
public string? Recommendation { get; init; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Crypto risk severity levels.
|
||||
/// </summary>
|
||||
public enum CryptoRiskSeverity
|
||||
{
|
||||
/// <summary>Informational only.</summary>
|
||||
Info,
|
||||
/// <summary>Low risk, may need future attention.</summary>
|
||||
Low,
|
||||
/// <summary>Medium risk, should be addressed.</summary>
|
||||
Medium,
|
||||
/// <summary>High risk, needs prompt attention.</summary>
|
||||
High,
|
||||
/// <summary>Critical risk, immediate action required.</summary>
|
||||
Critical
|
||||
}
|
||||
@@ -0,0 +1,508 @@
|
||||
using System.Collections.Immutable;
|
||||
using System.Text.Json;
|
||||
using System.Text.Json.Nodes;
|
||||
using StellaOps.Scanner.Emit.Cbom;
|
||||
|
||||
namespace StellaOps.Scanner.Emit.Composition;
|
||||
|
||||
/// <summary>
|
||||
/// Enhances CycloneDX 1.7 JSON with CBOM (Cryptographic BOM) properties.
|
||||
/// This is a post-processor that injects cryptographicProperties into components
|
||||
/// since CycloneDX.Core doesn't natively support 1.7 CBOM yet.
|
||||
/// </summary>
|
||||
public static class CycloneDxCbomWriter
|
||||
{
|
||||
/// <summary>
|
||||
/// Enhances a CycloneDX JSON with CBOM data.
|
||||
/// </summary>
|
||||
/// <param name="cycloneDxJson">The CycloneDX JSON (should be 1.7 format).</param>
|
||||
/// <param name="cryptoAssetsByComponent">Crypto assets indexed by component bom-ref.</param>
|
||||
/// <returns>Enhanced JSON with cryptographicProperties.</returns>
|
||||
public static string InjectCbom(
|
||||
string cycloneDxJson,
|
||||
ImmutableDictionary<string, ImmutableArray<CryptoAsset>> cryptoAssetsByComponent)
|
||||
{
|
||||
if (string.IsNullOrEmpty(cycloneDxJson) || cryptoAssetsByComponent.IsEmpty)
|
||||
{
|
||||
return cycloneDxJson;
|
||||
}
|
||||
|
||||
var root = JsonNode.Parse(cycloneDxJson);
|
||||
if (root is not JsonObject bomObj)
|
||||
{
|
||||
return cycloneDxJson;
|
||||
}
|
||||
|
||||
// Ensure specVersion is 1.7
|
||||
if (bomObj["specVersion"]?.GetValue<string>() is not "1.7")
|
||||
{
|
||||
bomObj["specVersion"] = "1.7";
|
||||
}
|
||||
|
||||
// Process components array
|
||||
if (bomObj["components"] is JsonArray componentsArray)
|
||||
{
|
||||
foreach (var componentNode in componentsArray)
|
||||
{
|
||||
if (componentNode is not JsonObject componentObj)
|
||||
continue;
|
||||
|
||||
var bomRef = componentObj["bom-ref"]?.GetValue<string>();
|
||||
if (string.IsNullOrEmpty(bomRef))
|
||||
continue;
|
||||
|
||||
if (cryptoAssetsByComponent.TryGetValue(bomRef, out var assets) && !assets.IsEmpty)
|
||||
{
|
||||
var cryptoProps = BuildCryptoProperties(assets);
|
||||
if (cryptoProps != null)
|
||||
{
|
||||
componentObj["cryptographicProperties"] = cryptoProps;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return bomObj.ToJsonString(new JsonSerializerOptions
|
||||
{
|
||||
WriteIndented = true,
|
||||
PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
|
||||
DefaultIgnoreCondition = System.Text.Json.Serialization.JsonIgnoreCondition.WhenWritingNull
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Enhances CycloneDX artifact bytes with CBOM data.
|
||||
/// </summary>
|
||||
public static byte[] InjectCbomBytes(
|
||||
byte[] cycloneDxJsonBytes,
|
||||
ImmutableDictionary<string, ImmutableArray<CryptoAsset>> cryptoAssetsByComponent)
|
||||
{
|
||||
var json = System.Text.Encoding.UTF8.GetString(cycloneDxJsonBytes);
|
||||
var enhanced = InjectCbom(json, cryptoAssetsByComponent);
|
||||
return System.Text.Encoding.UTF8.GetBytes(enhanced);
|
||||
}
|
||||
|
||||
private static JsonArray? BuildCryptoProperties(ImmutableArray<CryptoAsset> assets)
|
||||
{
|
||||
if (assets.IsDefaultOrEmpty)
|
||||
return null;
|
||||
|
||||
var cryptoArray = new JsonArray();
|
||||
|
||||
foreach (var asset in assets)
|
||||
{
|
||||
var cryptoObj = new JsonObject
|
||||
{
|
||||
["assetType"] = MapAssetType(asset.AssetType)
|
||||
};
|
||||
|
||||
// Add OID if present
|
||||
if (!string.IsNullOrEmpty(asset.Oid))
|
||||
{
|
||||
cryptoObj["oid"] = asset.Oid;
|
||||
}
|
||||
|
||||
// Build algorithm properties
|
||||
if (asset.AssetType == CryptoAssetType.Algorithm)
|
||||
{
|
||||
var algProps = BuildAlgorithmProperties(asset);
|
||||
if (algProps != null)
|
||||
{
|
||||
cryptoObj["algorithmProperties"] = algProps;
|
||||
}
|
||||
}
|
||||
// Build related crypto material properties
|
||||
else if (asset.AssetType == CryptoAssetType.RelatedCryptoMaterial && asset.CryptoProperties?.RelatedCryptoMaterialProperties != null)
|
||||
{
|
||||
var matProps = BuildRelatedCryptoMaterialProperties(asset.CryptoProperties.RelatedCryptoMaterialProperties);
|
||||
if (matProps != null)
|
||||
{
|
||||
cryptoObj["relatedCryptoMaterialProperties"] = matProps;
|
||||
}
|
||||
}
|
||||
// Build certificate properties
|
||||
else if (asset.AssetType == CryptoAssetType.Certificate && asset.CryptoProperties?.CertificateProperties != null)
|
||||
{
|
||||
var certProps = BuildCertificateProperties(asset.CryptoProperties.CertificateProperties);
|
||||
if (certProps != null)
|
||||
{
|
||||
cryptoObj["certificateProperties"] = certProps;
|
||||
}
|
||||
}
|
||||
// Build protocol properties
|
||||
else if (asset.AssetType == CryptoAssetType.Protocol && asset.CryptoProperties?.ProtocolProperties != null)
|
||||
{
|
||||
var protoProps = BuildProtocolProperties(asset.CryptoProperties.ProtocolProperties);
|
||||
if (protoProps != null)
|
||||
{
|
||||
cryptoObj["protocolProperties"] = protoProps;
|
||||
}
|
||||
}
|
||||
|
||||
cryptoArray.Add(cryptoObj);
|
||||
}
|
||||
|
||||
return cryptoArray.Count > 0 ? cryptoArray : null;
|
||||
}
|
||||
|
||||
private static JsonObject? BuildAlgorithmProperties(CryptoAsset asset)
|
||||
{
|
||||
var props = new JsonObject();
|
||||
|
||||
// Primitive
|
||||
if (asset.Primitive.HasValue)
|
||||
{
|
||||
props["primitive"] = MapPrimitive(asset.Primitive.Value);
|
||||
}
|
||||
|
||||
// Crypto functions
|
||||
if (!asset.Functions.IsDefaultOrEmpty)
|
||||
{
|
||||
var funcsArray = new JsonArray();
|
||||
foreach (var func in asset.Functions)
|
||||
{
|
||||
funcsArray.Add(MapFunction(func));
|
||||
}
|
||||
props["cryptoFunctions"] = funcsArray;
|
||||
}
|
||||
|
||||
// Mode
|
||||
if (asset.Mode.HasValue)
|
||||
{
|
||||
props["mode"] = MapMode(asset.Mode.Value);
|
||||
}
|
||||
|
||||
// Padding
|
||||
if (asset.Padding.HasValue)
|
||||
{
|
||||
props["padding"] = MapPadding(asset.Padding.Value);
|
||||
}
|
||||
|
||||
// Curve
|
||||
if (!string.IsNullOrEmpty(asset.Curve))
|
||||
{
|
||||
props["curve"] = asset.Curve;
|
||||
}
|
||||
|
||||
// Key size as parameter set identifier
|
||||
if (asset.KeySizeBits.HasValue)
|
||||
{
|
||||
props["parameterSetIdentifier"] = $"{asset.KeySizeBits.Value}";
|
||||
props["classicalSecurityLevel"] = asset.KeySizeBits.Value;
|
||||
}
|
||||
|
||||
// Execution environment
|
||||
if (asset.ExecutionEnvironment.HasValue)
|
||||
{
|
||||
props["executionEnvironment"] = MapExecutionEnvironment(asset.ExecutionEnvironment.Value);
|
||||
}
|
||||
|
||||
// Implementation platform
|
||||
if (!string.IsNullOrEmpty(asset.ImplementationPlatform))
|
||||
{
|
||||
props["implementationPlatform"] = asset.ImplementationPlatform;
|
||||
}
|
||||
|
||||
// Check for post-quantum and add NIST security level
|
||||
if (IsPostQuantumAlgorithm(asset.AlgorithmName))
|
||||
{
|
||||
// Typical NIST level for ML-KEM/ML-DSA is 1, 3, or 5
|
||||
props["nistQuantumSecurityLevel"] = GetNistQuantumLevel(asset.AlgorithmName);
|
||||
}
|
||||
|
||||
return props.Count > 0 ? props : null;
|
||||
}
|
||||
|
||||
private static JsonObject? BuildRelatedCryptoMaterialProperties(RelatedCryptoMaterialProperties material)
|
||||
{
|
||||
var props = new JsonObject();
|
||||
|
||||
if (material.Type.HasValue)
|
||||
{
|
||||
props["type"] = MapRelatedMaterialType(material.Type.Value);
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(material.Id))
|
||||
{
|
||||
props["id"] = material.Id;
|
||||
}
|
||||
|
||||
if (material.State.HasValue)
|
||||
{
|
||||
props["state"] = MapMaterialState(material.State.Value);
|
||||
}
|
||||
|
||||
if (material.Size.HasValue)
|
||||
{
|
||||
props["size"] = material.Size.Value;
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(material.AlgorithmRef))
|
||||
{
|
||||
props["algorithmRef"] = material.AlgorithmRef;
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(material.CreationDate))
|
||||
{
|
||||
props["creationDate"] = material.CreationDate;
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(material.ActivationDate))
|
||||
{
|
||||
props["activationDate"] = material.ActivationDate;
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(material.ExpirationDate))
|
||||
{
|
||||
props["expirationDate"] = material.ExpirationDate;
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(material.Format))
|
||||
{
|
||||
props["format"] = material.Format;
|
||||
}
|
||||
|
||||
return props.Count > 0 ? props : null;
|
||||
}
|
||||
|
||||
private static JsonObject? BuildCertificateProperties(CertificateProperties cert)
|
||||
{
|
||||
var props = new JsonObject();
|
||||
|
||||
if (!string.IsNullOrEmpty(cert.SubjectName))
|
||||
{
|
||||
props["subjectName"] = cert.SubjectName;
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(cert.IssuerName))
|
||||
{
|
||||
props["issuerName"] = cert.IssuerName;
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(cert.NotValidBefore))
|
||||
{
|
||||
props["notValidBefore"] = cert.NotValidBefore;
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(cert.NotValidAfter))
|
||||
{
|
||||
props["notValidAfter"] = cert.NotValidAfter;
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(cert.SignatureAlgorithmRef))
|
||||
{
|
||||
props["signatureAlgorithmRef"] = cert.SignatureAlgorithmRef;
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(cert.SubjectPublicKeyRef))
|
||||
{
|
||||
props["subjectPublicKeyRef"] = cert.SubjectPublicKeyRef;
|
||||
}
|
||||
|
||||
if (cert.CertificateFormat.HasValue)
|
||||
{
|
||||
props["certificateFormat"] = MapCertificateFormat(cert.CertificateFormat.Value);
|
||||
}
|
||||
|
||||
return props.Count > 0 ? props : null;
|
||||
}
|
||||
|
||||
private static JsonObject? BuildProtocolProperties(ProtocolProperties protocol)
|
||||
{
|
||||
var props = new JsonObject();
|
||||
|
||||
if (protocol.Type.HasValue)
|
||||
{
|
||||
props["type"] = MapProtocolType(protocol.Type.Value);
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(protocol.Version))
|
||||
{
|
||||
props["version"] = protocol.Version;
|
||||
}
|
||||
|
||||
if (!protocol.CipherSuites.IsDefaultOrEmpty)
|
||||
{
|
||||
var suitesArray = new JsonArray();
|
||||
foreach (var suite in protocol.CipherSuites)
|
||||
{
|
||||
var suiteObj = new JsonObject();
|
||||
if (!string.IsNullOrEmpty(suite.Name))
|
||||
{
|
||||
suiteObj["name"] = suite.Name;
|
||||
}
|
||||
if (!suite.Algorithms.IsDefaultOrEmpty)
|
||||
{
|
||||
var algsArray = new JsonArray();
|
||||
foreach (var alg in suite.Algorithms)
|
||||
{
|
||||
algsArray.Add(alg);
|
||||
}
|
||||
suiteObj["algorithms"] = algsArray;
|
||||
}
|
||||
suitesArray.Add(suiteObj);
|
||||
}
|
||||
props["cipherSuites"] = suitesArray;
|
||||
}
|
||||
|
||||
return props.Count > 0 ? props : null;
|
||||
}
|
||||
|
||||
private static string MapAssetType(CryptoAssetType type) => type switch
|
||||
{
|
||||
CryptoAssetType.Algorithm => "algorithm",
|
||||
CryptoAssetType.Certificate => "certificate",
|
||||
CryptoAssetType.Protocol => "protocol",
|
||||
CryptoAssetType.RelatedCryptoMaterial => "related-crypto-material",
|
||||
_ => "unknown"
|
||||
};
|
||||
|
||||
private static string MapPrimitive(CryptoPrimitive primitive) => primitive switch
|
||||
{
|
||||
CryptoPrimitive.Dlog => "dlog",
|
||||
CryptoPrimitive.Ec => "ec",
|
||||
CryptoPrimitive.Rsa => "rsa",
|
||||
CryptoPrimitive.Lattice => "lattice",
|
||||
CryptoPrimitive.Hash => "hash",
|
||||
CryptoPrimitive.BlockCipher => "block-cipher",
|
||||
CryptoPrimitive.StreamCipher => "stream-cipher",
|
||||
CryptoPrimitive.Aead => "aead",
|
||||
CryptoPrimitive.Mac => "mac",
|
||||
CryptoPrimitive.Kdf => "kdf",
|
||||
CryptoPrimitive.Kem => "kem",
|
||||
CryptoPrimitive.Pbkdf => "pbkdf",
|
||||
CryptoPrimitive.Signature => "signature",
|
||||
CryptoPrimitive.KeyAgree => "key-agree",
|
||||
CryptoPrimitive.Prng => "prng",
|
||||
_ => "other"
|
||||
};
|
||||
|
||||
private static string MapFunction(CryptoFunction func) => func switch
|
||||
{
|
||||
CryptoFunction.Generate => "generate",
|
||||
CryptoFunction.Keygen => "keygen",
|
||||
CryptoFunction.Derive => "derive",
|
||||
CryptoFunction.Sign => "sign",
|
||||
CryptoFunction.Verify => "verify",
|
||||
CryptoFunction.Encrypt => "encrypt",
|
||||
CryptoFunction.Decrypt => "decrypt",
|
||||
CryptoFunction.Encapsulate => "encapsulate",
|
||||
CryptoFunction.Decapsulate => "decapsulate",
|
||||
CryptoFunction.Digest => "digest",
|
||||
CryptoFunction.Tag => "tag",
|
||||
CryptoFunction.KeyWrap => "key-wrap",
|
||||
CryptoFunction.KeyUnwrap => "key-unwrap",
|
||||
CryptoFunction.KeyAgree => "key-agree",
|
||||
_ => "other"
|
||||
};
|
||||
|
||||
private static string MapMode(CryptoMode mode) => mode switch
|
||||
{
|
||||
CryptoMode.Cbc => "cbc",
|
||||
CryptoMode.Ecb => "ecb",
|
||||
CryptoMode.Ccm => "ccm",
|
||||
CryptoMode.Gcm => "gcm",
|
||||
CryptoMode.Cfb => "cfb",
|
||||
CryptoMode.Ofb => "ofb",
|
||||
CryptoMode.Ctr => "ctr",
|
||||
CryptoMode.Xts => "xts",
|
||||
CryptoMode.Wrap => "wrap",
|
||||
_ => "other"
|
||||
};
|
||||
|
||||
private static string MapPadding(CryptoPadding padding) => padding switch
|
||||
{
|
||||
CryptoPadding.Pkcs7 => "pkcs7",
|
||||
CryptoPadding.Oaep => "oaep",
|
||||
CryptoPadding.Pkcs1v15 => "pkcs1v15",
|
||||
CryptoPadding.Pss => "pss",
|
||||
CryptoPadding.X923 => "x923",
|
||||
CryptoPadding.Raw => "raw",
|
||||
CryptoPadding.None => "none",
|
||||
_ => "other"
|
||||
};
|
||||
|
||||
private static string MapExecutionEnvironment(ExecutionEnvironment env) => env switch
|
||||
{
|
||||
ExecutionEnvironment.Software => "software",
|
||||
ExecutionEnvironment.HardwareSecurityModule => "hardware-security-module",
|
||||
ExecutionEnvironment.TrustedExecutionEnvironment => "trusted-execution-environment",
|
||||
ExecutionEnvironment.Hardware => "hardware",
|
||||
_ => "unknown"
|
||||
};
|
||||
|
||||
private static string MapRelatedMaterialType(RelatedCryptoMaterialType type) => type switch
|
||||
{
|
||||
RelatedCryptoMaterialType.PrivateKey => "private-key",
|
||||
RelatedCryptoMaterialType.PublicKey => "public-key",
|
||||
RelatedCryptoMaterialType.SecretKey => "secret-key",
|
||||
RelatedCryptoMaterialType.Key => "key",
|
||||
RelatedCryptoMaterialType.Nonce => "nonce",
|
||||
RelatedCryptoMaterialType.Seed => "seed",
|
||||
RelatedCryptoMaterialType.Iv => "iv",
|
||||
RelatedCryptoMaterialType.Salt => "salt",
|
||||
RelatedCryptoMaterialType.SharedSecret => "shared-secret",
|
||||
_ => "other"
|
||||
};
|
||||
|
||||
private static string MapMaterialState(CryptoMaterialState state) => state switch
|
||||
{
|
||||
CryptoMaterialState.PreActivation => "pre-activation",
|
||||
CryptoMaterialState.Active => "active",
|
||||
CryptoMaterialState.Suspended => "suspended",
|
||||
CryptoMaterialState.Deactivated => "deactivated",
|
||||
CryptoMaterialState.Compromised => "compromised",
|
||||
CryptoMaterialState.Destroyed => "destroyed",
|
||||
_ => "unknown"
|
||||
};
|
||||
|
||||
private static string MapCertificateFormat(CertificateFormat format) => format switch
|
||||
{
|
||||
CertificateFormat.X509 => "X.509",
|
||||
CertificateFormat.Pgp => "PGP",
|
||||
CertificateFormat.Pkcs7 => "PKCS#7",
|
||||
_ => "other"
|
||||
};
|
||||
|
||||
private static string MapProtocolType(ProtocolType type) => type switch
|
||||
{
|
||||
ProtocolType.Tls => "tls",
|
||||
ProtocolType.Ssh => "ssh",
|
||||
ProtocolType.Ipsec => "ipsec",
|
||||
ProtocolType.Ike => "ike",
|
||||
ProtocolType.Sstp => "sstp",
|
||||
ProtocolType.Wpa => "wpa",
|
||||
_ => "other"
|
||||
};
|
||||
|
||||
private static bool IsPostQuantumAlgorithm(string? algorithmName)
|
||||
{
|
||||
if (string.IsNullOrEmpty(algorithmName))
|
||||
return false;
|
||||
|
||||
var upper = algorithmName.ToUpperInvariant();
|
||||
return upper.Contains("KYBER") || upper.Contains("ML-KEM") ||
|
||||
upper.Contains("DILITHIUM") || upper.Contains("ML-DSA") ||
|
||||
upper.Contains("SPHINCS") || upper.Contains("SLH-DSA") ||
|
||||
upper.Contains("FALCON") || upper.Contains("NTRU") ||
|
||||
upper.Contains("FRODO") || upper.Contains("SABER");
|
||||
}
|
||||
|
||||
private static int GetNistQuantumLevel(string? algorithmName)
|
||||
{
|
||||
if (string.IsNullOrEmpty(algorithmName))
|
||||
return 1;
|
||||
|
||||
var upper = algorithmName.ToUpperInvariant();
|
||||
|
||||
// ML-KEM-768 / Dilithium3 => Level 3
|
||||
// ML-KEM-1024 / Dilithium5 => Level 5
|
||||
if (upper.Contains("1024") || upper.Contains("5"))
|
||||
return 5;
|
||||
if (upper.Contains("768") || upper.Contains("3"))
|
||||
return 3;
|
||||
|
||||
// Default to Level 1 (ML-KEM-512 / Dilithium2)
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
@@ -14,8 +14,8 @@
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="CycloneDX.Core" Version="10.0.2" />
|
||||
<PackageReference Include="RoaringBitmap" Version="0.0.9" />
|
||||
<PackageReference Include="CycloneDX.Core" />
|
||||
<PackageReference Include="RoaringBitmap" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
||||
Reference in New Issue
Block a user