Fix build and code structure improvements. New but essential UI functionality. CI improvements. Documentation improvements. AI module improvements.
This commit is contained in:
@@ -0,0 +1,358 @@
|
||||
using System.Collections.Immutable;
|
||||
using StellaOps.Scanner.Core.Contracts;
|
||||
using StellaOps.Scanner.Emit.Cbom;
|
||||
|
||||
namespace StellaOps.Scanner.Analyzers.Lang.DotNet.Internal.Crypto;
|
||||
|
||||
/// <summary>
|
||||
/// Extracts cryptographic assets from .NET assemblies and NuGet packages.
|
||||
/// Analyzes System.Security.Cryptography usage patterns.
|
||||
/// </summary>
|
||||
public sealed class DotNetCryptoExtractor : ICryptoAssetExtractor
|
||||
{
|
||||
private static readonly ImmutableArray<string> Ecosystems = ImmutableArray.Create("nuget", "dotnet");
|
||||
|
||||
public ImmutableArray<string> SupportedEcosystems => Ecosystems;
|
||||
|
||||
/// <summary>
|
||||
/// Known crypto-related NuGet packages.
|
||||
/// </summary>
|
||||
private static readonly ImmutableHashSet<string> CryptoPackages = ImmutableHashSet.Create(
|
||||
StringComparer.OrdinalIgnoreCase,
|
||||
"System.Security.Cryptography.Algorithms",
|
||||
"System.Security.Cryptography.Cng",
|
||||
"System.Security.Cryptography.Csp",
|
||||
"System.Security.Cryptography.OpenSsl",
|
||||
"System.Security.Cryptography.Pkcs",
|
||||
"System.Security.Cryptography.ProtectedData",
|
||||
"System.Security.Cryptography.X509Certificates",
|
||||
"System.Security.Cryptography.Xml",
|
||||
"BouncyCastle.Cryptography",
|
||||
"BouncyCastle.NetCore",
|
||||
"Portable.BouncyCastle",
|
||||
"libsodium",
|
||||
"NSec.Cryptography",
|
||||
"Microsoft.IdentityModel.Tokens",
|
||||
"System.IdentityModel.Tokens.Jwt",
|
||||
"Jose-jwt",
|
||||
"jose-jwt",
|
||||
"BCrypt.Net-Next",
|
||||
"Scrypt.NET",
|
||||
"Argon2.NetCore",
|
||||
"Konscious.Security.Cryptography.Argon2",
|
||||
"CryptoNet",
|
||||
"NaCl.Core"
|
||||
);
|
||||
|
||||
/// <summary>
|
||||
/// Algorithm patterns to detect in package names and identifiers.
|
||||
/// </summary>
|
||||
private static readonly ImmutableArray<CryptoAlgorithmPattern> AlgorithmPatterns = ImmutableArray.Create(
|
||||
// Hash algorithms
|
||||
new CryptoAlgorithmPattern("MD5", "1.2.840.113549.2.5", CryptoPrimitive.Hash, CryptoFunction.Digest, 128, IsDeprecated: true),
|
||||
new CryptoAlgorithmPattern("SHA1", "1.3.14.3.2.26", CryptoPrimitive.Hash, CryptoFunction.Digest, 160, IsDeprecated: true),
|
||||
new CryptoAlgorithmPattern("SHA-1", "1.3.14.3.2.26", CryptoPrimitive.Hash, CryptoFunction.Digest, 160, IsDeprecated: true),
|
||||
new CryptoAlgorithmPattern("SHA256", "2.16.840.1.101.3.4.2.1", CryptoPrimitive.Hash, CryptoFunction.Digest, 256),
|
||||
new CryptoAlgorithmPattern("SHA-256", "2.16.840.1.101.3.4.2.1", CryptoPrimitive.Hash, CryptoFunction.Digest, 256),
|
||||
new CryptoAlgorithmPattern("SHA384", "2.16.840.1.101.3.4.2.2", CryptoPrimitive.Hash, CryptoFunction.Digest, 384),
|
||||
new CryptoAlgorithmPattern("SHA512", "2.16.840.1.101.3.4.2.3", CryptoPrimitive.Hash, CryptoFunction.Digest, 512),
|
||||
new CryptoAlgorithmPattern("SHA3-256", "2.16.840.1.101.3.4.2.8", CryptoPrimitive.Hash, CryptoFunction.Digest, 256),
|
||||
new CryptoAlgorithmPattern("SHA3-512", "2.16.840.1.101.3.4.2.10", CryptoPrimitive.Hash, CryptoFunction.Digest, 512),
|
||||
|
||||
// Symmetric ciphers
|
||||
new CryptoAlgorithmPattern("AES", "2.16.840.1.101.3.4.1", CryptoPrimitive.BlockCipher, CryptoFunction.Encrypt, 256),
|
||||
new CryptoAlgorithmPattern("AES-128", "2.16.840.1.101.3.4.1.1", CryptoPrimitive.BlockCipher, CryptoFunction.Encrypt, 128),
|
||||
new CryptoAlgorithmPattern("AES-192", "2.16.840.1.101.3.4.1.21", CryptoPrimitive.BlockCipher, CryptoFunction.Encrypt, 192),
|
||||
new CryptoAlgorithmPattern("AES-256", "2.16.840.1.101.3.4.1.41", CryptoPrimitive.BlockCipher, CryptoFunction.Encrypt, 256),
|
||||
new CryptoAlgorithmPattern("AES-GCM", "2.16.840.1.101.3.4.1.46", CryptoPrimitive.Aead, CryptoFunction.Encrypt, 256),
|
||||
new CryptoAlgorithmPattern("DES", "1.3.14.3.2.7", CryptoPrimitive.BlockCipher, CryptoFunction.Encrypt, 56, IsDeprecated: true),
|
||||
new CryptoAlgorithmPattern("3DES", "1.2.840.113549.3.7", CryptoPrimitive.BlockCipher, CryptoFunction.Encrypt, 168, IsWeak: true),
|
||||
new CryptoAlgorithmPattern("TripleDES", "1.2.840.113549.3.7", CryptoPrimitive.BlockCipher, CryptoFunction.Encrypt, 168, IsWeak: true),
|
||||
new CryptoAlgorithmPattern("ChaCha20", null, CryptoPrimitive.StreamCipher, CryptoFunction.Encrypt, 256),
|
||||
new CryptoAlgorithmPattern("ChaCha20Poly1305", null, CryptoPrimitive.Aead, CryptoFunction.Encrypt, 256),
|
||||
new CryptoAlgorithmPattern("RC4", null, CryptoPrimitive.StreamCipher, CryptoFunction.Encrypt, 128, IsDeprecated: true),
|
||||
new CryptoAlgorithmPattern("Blowfish", null, CryptoPrimitive.BlockCipher, CryptoFunction.Encrypt, 128, IsWeak: true),
|
||||
|
||||
// Asymmetric algorithms
|
||||
new CryptoAlgorithmPattern("RSA", "1.2.840.113549.1.1.1", CryptoPrimitive.Rsa, CryptoFunction.Sign, 2048, IsQuantumVulnerable: true),
|
||||
new CryptoAlgorithmPattern("DSA", "1.2.840.10040.4.1", CryptoPrimitive.Dlog, CryptoFunction.Sign, 2048, IsQuantumVulnerable: true),
|
||||
new CryptoAlgorithmPattern("ECDSA", "1.2.840.10045.4.3", CryptoPrimitive.Ec, CryptoFunction.Sign, 256, IsQuantumVulnerable: true),
|
||||
new CryptoAlgorithmPattern("ECDH", "1.3.132.1.12", CryptoPrimitive.Ec, CryptoFunction.KeyAgree, 256, IsQuantumVulnerable: true),
|
||||
new CryptoAlgorithmPattern("DiffieHellman", null, CryptoPrimitive.Dlog, CryptoFunction.KeyAgree, 2048, IsQuantumVulnerable: true),
|
||||
|
||||
// Key derivation
|
||||
new CryptoAlgorithmPattern("PBKDF2", "1.2.840.113549.1.5.12", CryptoPrimitive.Pbkdf, CryptoFunction.Derive, 256),
|
||||
new CryptoAlgorithmPattern("Rfc2898", "1.2.840.113549.1.5.12", CryptoPrimitive.Pbkdf, CryptoFunction.Derive, 256),
|
||||
new CryptoAlgorithmPattern("HKDF", null, CryptoPrimitive.Kdf, CryptoFunction.Derive, 256),
|
||||
new CryptoAlgorithmPattern("BCrypt", null, CryptoPrimitive.Pbkdf, CryptoFunction.Derive, 184),
|
||||
new CryptoAlgorithmPattern("SCrypt", null, CryptoPrimitive.Pbkdf, CryptoFunction.Derive, 256),
|
||||
new CryptoAlgorithmPattern("Argon2", null, CryptoPrimitive.Pbkdf, CryptoFunction.Derive, 256),
|
||||
|
||||
// MACs
|
||||
new CryptoAlgorithmPattern("HMAC", null, CryptoPrimitive.Mac, CryptoFunction.Tag, 256),
|
||||
new CryptoAlgorithmPattern("HMACSHA256", "1.2.840.113549.2.9", CryptoPrimitive.Mac, CryptoFunction.Tag, 256),
|
||||
new CryptoAlgorithmPattern("HMACSHA512", "1.2.840.113549.2.11", CryptoPrimitive.Mac, CryptoFunction.Tag, 512),
|
||||
new CryptoAlgorithmPattern("HMACMD5", "1.3.6.1.5.5.8.1.1", CryptoPrimitive.Mac, CryptoFunction.Tag, 128, IsDeprecated: true),
|
||||
|
||||
// Post-quantum (emerging in .NET)
|
||||
new CryptoAlgorithmPattern("ML-KEM", null, CryptoPrimitive.Kem, CryptoFunction.Encapsulate, 256, IsPostQuantum: true),
|
||||
new CryptoAlgorithmPattern("ML-DSA", null, CryptoPrimitive.Lattice, CryptoFunction.Sign, 256, IsPostQuantum: true),
|
||||
new CryptoAlgorithmPattern("SLH-DSA", null, CryptoPrimitive.Hash, CryptoFunction.Sign, 256, IsPostQuantum: true)
|
||||
);
|
||||
|
||||
public Task<ImmutableArray<CryptoAsset>> ExtractAsync(
|
||||
AggregatedComponent component,
|
||||
CryptoAnalysisContext analysisContext,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
var assets = new List<CryptoAsset>();
|
||||
|
||||
// Check if component is a known crypto package
|
||||
var packageName = component.Identity.Name ?? string.Empty;
|
||||
var purl = component.Identity.Purl ?? string.Empty;
|
||||
|
||||
// Skip if not a .NET package
|
||||
if (!purl.StartsWith("pkg:nuget/", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return Task.FromResult(ImmutableArray<CryptoAsset>.Empty);
|
||||
}
|
||||
|
||||
// Check for known crypto packages
|
||||
if (CryptoPackages.Contains(packageName))
|
||||
{
|
||||
var cryptoAssets = ExtractFromKnownPackage(component, packageName);
|
||||
assets.AddRange(cryptoAssets);
|
||||
}
|
||||
|
||||
// Check package name for algorithm patterns
|
||||
foreach (var pattern in AlgorithmPatterns)
|
||||
{
|
||||
if (packageName.Contains(pattern.Name, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
var asset = CreateAssetFromPattern(component, pattern);
|
||||
if (!assets.Any(a => a.AlgorithmName == asset.AlgorithmName))
|
||||
{
|
||||
assets.Add(asset);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Check metadata for crypto evidence
|
||||
if (component.Metadata?.Properties != null)
|
||||
{
|
||||
foreach (var (key, value) in component.Metadata.Properties)
|
||||
{
|
||||
foreach (var pattern in AlgorithmPatterns)
|
||||
{
|
||||
if (value.Contains(pattern.Name, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
var asset = CreateAssetFromPattern(component, pattern, $"property:{key}");
|
||||
if (!assets.Any(a => a.AlgorithmName == asset.AlgorithmName))
|
||||
{
|
||||
assets.Add(asset);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return Task.FromResult(assets.ToImmutableArray());
|
||||
}
|
||||
|
||||
private static IEnumerable<CryptoAsset> ExtractFromKnownPackage(AggregatedComponent component, string packageName)
|
||||
{
|
||||
var assets = new List<CryptoAsset>();
|
||||
|
||||
// System.Security.Cryptography packages include multiple algorithms
|
||||
if (packageName.StartsWith("System.Security.Cryptography", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
// Add common algorithms from this namespace
|
||||
var commonAlgorithms = new[]
|
||||
{
|
||||
AlgorithmPatterns.First(p => p.Name == "AES"),
|
||||
AlgorithmPatterns.First(p => p.Name == "SHA256"),
|
||||
AlgorithmPatterns.First(p => p.Name == "RSA"),
|
||||
AlgorithmPatterns.First(p => p.Name == "ECDSA"),
|
||||
AlgorithmPatterns.First(p => p.Name == "HMACSHA256")
|
||||
};
|
||||
|
||||
foreach (var pattern in commonAlgorithms)
|
||||
{
|
||||
assets.Add(CreateAssetFromPattern(component, pattern, $"package:{packageName}"));
|
||||
}
|
||||
}
|
||||
else if (packageName.Contains("BouncyCastle", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
// BouncyCastle provides many algorithms
|
||||
var bcAlgorithms = AlgorithmPatterns.Where(p =>
|
||||
p.Name == "AES" || p.Name == "RSA" || p.Name == "ECDSA" ||
|
||||
p.Name == "SHA256" || p.Name == "SHA512" || p.Name == "ChaCha20Poly1305");
|
||||
|
||||
foreach (var pattern in bcAlgorithms)
|
||||
{
|
||||
assets.Add(CreateAssetFromPattern(component, pattern, $"package:{packageName}",
|
||||
implementationPlatform: "BouncyCastle"));
|
||||
}
|
||||
}
|
||||
else if (packageName.Contains("libsodium", StringComparison.OrdinalIgnoreCase) ||
|
||||
packageName.Contains("NSec", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
// Modern crypto libraries
|
||||
var modernAlgorithms = new[]
|
||||
{
|
||||
AlgorithmPatterns.First(p => p.Name == "ChaCha20Poly1305"),
|
||||
AlgorithmPatterns.First(p => p.Name == "AES-GCM"),
|
||||
AlgorithmPatterns.First(p => p.Name == "SHA512")
|
||||
};
|
||||
|
||||
foreach (var pattern in modernAlgorithms)
|
||||
{
|
||||
assets.Add(CreateAssetFromPattern(component, pattern, $"package:{packageName}",
|
||||
implementationPlatform: "libsodium"));
|
||||
}
|
||||
}
|
||||
else if (packageName.Contains("BCrypt", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
assets.Add(CreateAssetFromPattern(component,
|
||||
AlgorithmPatterns.First(p => p.Name == "BCrypt"), $"package:{packageName}"));
|
||||
}
|
||||
else if (packageName.Contains("Argon2", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
assets.Add(CreateAssetFromPattern(component,
|
||||
AlgorithmPatterns.First(p => p.Name == "Argon2"), $"package:{packageName}"));
|
||||
}
|
||||
else if (packageName.Contains("Jwt", StringComparison.OrdinalIgnoreCase) ||
|
||||
packageName.Contains("Jose", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
// JWT libraries use various algorithms
|
||||
var jwtAlgorithms = new[]
|
||||
{
|
||||
AlgorithmPatterns.First(p => p.Name == "RSA"),
|
||||
AlgorithmPatterns.First(p => p.Name == "ECDSA"),
|
||||
AlgorithmPatterns.First(p => p.Name == "HMACSHA256"),
|
||||
AlgorithmPatterns.First(p => p.Name == "AES")
|
||||
};
|
||||
|
||||
foreach (var pattern in jwtAlgorithms)
|
||||
{
|
||||
assets.Add(CreateAssetFromPattern(component, pattern, $"package:{packageName}"));
|
||||
}
|
||||
}
|
||||
|
||||
return assets;
|
||||
}
|
||||
|
||||
private static CryptoAsset CreateAssetFromPattern(
|
||||
AggregatedComponent component,
|
||||
CryptoAlgorithmPattern pattern,
|
||||
string? evidenceSource = null,
|
||||
string? implementationPlatform = null)
|
||||
{
|
||||
var riskFlags = new List<CryptoRiskFlag>();
|
||||
|
||||
if (pattern.IsDeprecated)
|
||||
{
|
||||
riskFlags.Add(new CryptoRiskFlag
|
||||
{
|
||||
RiskId = "DEPRECATED_ALGORITHM",
|
||||
Severity = CryptoRiskSeverity.Critical,
|
||||
Description = $"{pattern.Name} is deprecated and should not be used",
|
||||
Recommendation = GetDeprecatedRecommendation(pattern.Name)
|
||||
});
|
||||
}
|
||||
|
||||
if (pattern.IsWeak)
|
||||
{
|
||||
riskFlags.Add(new CryptoRiskFlag
|
||||
{
|
||||
RiskId = "WEAK_ALGORITHM",
|
||||
Severity = CryptoRiskSeverity.High,
|
||||
Description = $"{pattern.Name} is considered weak by modern standards",
|
||||
Recommendation = GetWeakRecommendation(pattern.Name)
|
||||
});
|
||||
}
|
||||
|
||||
if (pattern.IsQuantumVulnerable)
|
||||
{
|
||||
riskFlags.Add(new CryptoRiskFlag
|
||||
{
|
||||
RiskId = "QUANTUM_VULNERABLE",
|
||||
Severity = CryptoRiskSeverity.Medium,
|
||||
Description = $"{pattern.Name} is vulnerable to quantum computing attacks",
|
||||
Recommendation = "Consider migration path to post-quantum algorithms (ML-KEM, ML-DSA)"
|
||||
});
|
||||
}
|
||||
|
||||
var evidence = new List<string> { $"component:{component.Identity.Key}" };
|
||||
if (evidenceSource != null)
|
||||
{
|
||||
evidence.Add(evidenceSource);
|
||||
}
|
||||
|
||||
var algorithmProperties = new AlgorithmProperties
|
||||
{
|
||||
Primitive = pattern.Primitive,
|
||||
CryptoFunctions = ImmutableArray.Create(pattern.Function),
|
||||
ClassicalSecurityLevel = pattern.KeySize,
|
||||
ImplementationPlatform = implementationPlatform ?? "System.Security.Cryptography",
|
||||
ExecutionEnvironment = ExecutionEnvironment.Software
|
||||
};
|
||||
|
||||
return new CryptoAsset
|
||||
{
|
||||
Id = $"crypto:{component.Identity.Key}:{pattern.Name}",
|
||||
ComponentKey = component.Identity.Key,
|
||||
AssetType = CryptoAssetType.Algorithm,
|
||||
AlgorithmName = pattern.Name,
|
||||
Oid = pattern.Oid,
|
||||
KeySizeBits = pattern.KeySize,
|
||||
Primitive = pattern.Primitive,
|
||||
Functions = ImmutableArray.Create(pattern.Function),
|
||||
ImplementationPlatform = implementationPlatform ?? "System.Security.Cryptography",
|
||||
ExecutionEnvironment = ExecutionEnvironment.Software,
|
||||
Confidence = 0.9,
|
||||
Evidence = evidence.ToImmutableArray(),
|
||||
RiskFlags = riskFlags.ToImmutableArray(),
|
||||
CryptoProperties = new CryptoProperties
|
||||
{
|
||||
AssetType = CryptoAssetType.Algorithm,
|
||||
AlgorithmProperties = algorithmProperties,
|
||||
Oid = pattern.Oid
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
private static string GetDeprecatedRecommendation(string algorithm)
|
||||
{
|
||||
return algorithm.ToUpperInvariant() switch
|
||||
{
|
||||
"MD5" => "Replace with SHA-256 or SHA-3-256",
|
||||
"SHA1" or "SHA-1" => "Replace with SHA-256 or SHA-3-256",
|
||||
"DES" => "Replace with AES-256-GCM",
|
||||
"RC4" => "Replace with ChaCha20-Poly1305 or AES-GCM",
|
||||
"HMACMD5" => "Replace with HMAC-SHA256",
|
||||
_ => "Replace with a modern algorithm"
|
||||
};
|
||||
}
|
||||
|
||||
private static string GetWeakRecommendation(string algorithm)
|
||||
{
|
||||
return algorithm.ToUpperInvariant() switch
|
||||
{
|
||||
"3DES" or "TRIPLEDES" => "Replace with AES-256-GCM",
|
||||
"BLOWFISH" => "Replace with AES-256-GCM or ChaCha20-Poly1305",
|
||||
_ => "Consider using a stronger algorithm"
|
||||
};
|
||||
}
|
||||
|
||||
private sealed record CryptoAlgorithmPattern(
|
||||
string Name,
|
||||
string? Oid,
|
||||
CryptoPrimitive Primitive,
|
||||
CryptoFunction Function,
|
||||
int KeySize,
|
||||
bool IsDeprecated = false,
|
||||
bool IsWeak = false,
|
||||
bool IsQuantumVulnerable = false,
|
||||
bool IsPostQuantum = false);
|
||||
}
|
||||
@@ -20,5 +20,6 @@
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\StellaOps.Scanner.Analyzers.Lang\StellaOps.Scanner.Analyzers.Lang.csproj" />
|
||||
<ProjectReference Include="..\StellaOps.Scanner.Emit\StellaOps.Scanner.Emit.csproj" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
|
||||
Reference in New Issue
Block a user