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,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;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user