feat: Implement BerkeleyDB reader for RPM databases
Some checks failed
AOC Guard CI / aoc-guard (push) Has been cancelled
AOC Guard CI / aoc-verify (push) Has been cancelled
Concelier Attestation Tests / attestation-tests (push) Has been cancelled
Docs CI / lint-and-preview (push) Has been cancelled
Policy Lint & Smoke / policy-lint (push) Has been cancelled
Scanner Analyzers / Discover Analyzers (push) Has been cancelled
Scanner Analyzers / Build Analyzers (push) Has been cancelled
Scanner Analyzers / Test Language Analyzers (push) Has been cancelled
Scanner Analyzers / Validate Test Fixtures (push) Has been cancelled
Scanner Analyzers / Verify Deterministic Output (push) Has been cancelled
console-runner-image / build-runner-image (push) Has been cancelled
wine-csp-build / Build Wine CSP Image (push) Has been cancelled
wine-csp-build / Integration Tests (push) Has been cancelled
wine-csp-build / Security Scan (push) Has been cancelled
wine-csp-build / Generate SBOM (push) Has been cancelled
wine-csp-build / Publish Image (push) Has been cancelled
wine-csp-build / Air-Gap Bundle (push) Has been cancelled
wine-csp-build / Test Summary (push) Has been cancelled
Some checks failed
AOC Guard CI / aoc-guard (push) Has been cancelled
AOC Guard CI / aoc-verify (push) Has been cancelled
Concelier Attestation Tests / attestation-tests (push) Has been cancelled
Docs CI / lint-and-preview (push) Has been cancelled
Policy Lint & Smoke / policy-lint (push) Has been cancelled
Scanner Analyzers / Discover Analyzers (push) Has been cancelled
Scanner Analyzers / Build Analyzers (push) Has been cancelled
Scanner Analyzers / Test Language Analyzers (push) Has been cancelled
Scanner Analyzers / Validate Test Fixtures (push) Has been cancelled
Scanner Analyzers / Verify Deterministic Output (push) Has been cancelled
console-runner-image / build-runner-image (push) Has been cancelled
wine-csp-build / Build Wine CSP Image (push) Has been cancelled
wine-csp-build / Integration Tests (push) Has been cancelled
wine-csp-build / Security Scan (push) Has been cancelled
wine-csp-build / Generate SBOM (push) Has been cancelled
wine-csp-build / Publish Image (push) Has been cancelled
wine-csp-build / Air-Gap Bundle (push) Has been cancelled
wine-csp-build / Test Summary (push) Has been cancelled
- Added BerkeleyDbReader class to read and extract RPM header blobs from BerkeleyDB hash databases. - Implemented methods to detect BerkeleyDB format and extract values, including handling of page sizes and magic numbers. - Added tests for BerkeleyDbReader to ensure correct functionality and header extraction. feat: Add Yarn PnP data tests - Created YarnPnpDataTests to validate package resolution and data loading from Yarn PnP cache. - Implemented tests for resolved keys, package presence, and loading from cache structure. test: Add egg-info package fixtures for Python tests - Created egg-info package fixtures for testing Python analyzers. - Included PKG-INFO, entry_points.txt, and installed-files.txt for comprehensive coverage. test: Enhance RPM database reader tests - Added tests for RpmDatabaseReader to validate fallback to legacy packages when SQLite is missing. - Implemented helper methods to create legacy package files and RPM headers for testing. test: Implement dual signing tests - Added DualSignTests to validate secondary signature addition when configured. - Created stub implementations for crypto providers and key resolvers to facilitate testing. chore: Update CI script for Playwright Chromium installation - Modified ci-console-exports.sh to ensure deterministic Chromium binary installation for console exports tests. - Added checks for Windows compatibility and environment variable setups for Playwright browsers.
This commit is contained in:
@@ -48,7 +48,7 @@ public sealed class CryptoDsseSigner : IDsseSigner
|
||||
ArgumentNullException.ThrowIfNull(caller);
|
||||
|
||||
var signingMode = request.Options.Mode;
|
||||
var algorithmId = ResolveAlgorithm(signingMode);
|
||||
var algorithmId = ResolveAndValidateAlgorithm(null, signingMode);
|
||||
|
||||
_logger.LogDebug(
|
||||
"Starting DSSE signing for tenant {Tenant} with mode {Mode} and algorithm {Algorithm}",
|
||||
@@ -84,7 +84,7 @@ public sealed class CryptoDsseSigner : IDsseSigner
|
||||
|
||||
var signer = signerResolution.Signer;
|
||||
|
||||
// Sign the PAE
|
||||
// Sign the PAE (primary)
|
||||
var signatureBytes = await signer
|
||||
.SignAsync(paeBytes, cancellationToken)
|
||||
.ConfigureAwait(false);
|
||||
@@ -104,16 +104,47 @@ public sealed class CryptoDsseSigner : IDsseSigner
|
||||
// Build certificate chain if available
|
||||
var certChain = BuildCertificateChain(signer, keyResolution);
|
||||
|
||||
var signatures = new List<DsseSignature>
|
||||
{
|
||||
new DsseSignature(signatureBase64, signer.KeyId)
|
||||
};
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(_options.SecondaryAlgorithm))
|
||||
{
|
||||
var secondaryAlgorithm = ResolveAndValidateAlgorithm(_options.SecondaryAlgorithm, signingMode);
|
||||
var secondaryKeyId = _options.SecondaryKeyId ?? keyReference.KeyId;
|
||||
var secondaryProviderHint = _options.SecondaryProvider ?? keyResolution.ProviderHint;
|
||||
var secondaryRef = new CryptoKeyReference(secondaryKeyId, secondaryProviderHint);
|
||||
|
||||
var secondaryResolution = _cryptoRegistry.ResolveSigner(
|
||||
CryptoCapability.Signing,
|
||||
secondaryAlgorithm,
|
||||
secondaryRef,
|
||||
secondaryProviderHint);
|
||||
|
||||
var secondarySignatureBytes = await secondaryResolution.Signer
|
||||
.SignAsync(paeBytes, cancellationToken)
|
||||
.ConfigureAwait(false);
|
||||
|
||||
var secondarySignatureBase64 = Convert.ToBase64String(secondarySignatureBytes)
|
||||
.TrimEnd('=')
|
||||
.Replace('+', '-')
|
||||
.Replace('/', '_');
|
||||
|
||||
signatures.Add(new DsseSignature(secondarySignatureBase64, secondaryResolution.Signer.KeyId));
|
||||
|
||||
_logger.LogInformation(
|
||||
"Added secondary DSSE signature using provider {Provider} algorithm {Algorithm} key {KeyId}",
|
||||
secondaryResolution.ProviderName,
|
||||
secondaryAlgorithm,
|
||||
secondaryResolution.Signer.KeyId);
|
||||
}
|
||||
|
||||
// Build DSSE envelope
|
||||
var envelope = new DsseEnvelope(
|
||||
Payload: payloadBase64,
|
||||
PayloadType: DssePayloadType,
|
||||
Signatures:
|
||||
[
|
||||
new DsseSignature(
|
||||
Signature: signatureBase64,
|
||||
KeyId: signer.KeyId)
|
||||
]);
|
||||
Signatures: signatures);
|
||||
|
||||
// Build signing metadata
|
||||
var identity = new SigningIdentity(
|
||||
@@ -182,25 +213,37 @@ public sealed class CryptoDsseSigner : IDsseSigner
|
||||
return pae;
|
||||
}
|
||||
|
||||
private string ResolveAlgorithm(SigningMode mode)
|
||||
private string ResolveAndValidateAlgorithm(string? preferredOverride, SigningMode mode)
|
||||
{
|
||||
var preferred = mode switch
|
||||
{
|
||||
SigningMode.Keyless => _options.KeylessAlgorithm ?? SignatureAlgorithms.Es256,
|
||||
SigningMode.Kms => _options.KmsAlgorithm ?? SignatureAlgorithms.Es256,
|
||||
SigningMode.Keyless => preferredOverride ?? _options.KeylessAlgorithm ?? SignatureAlgorithms.Es256,
|
||||
SigningMode.Kms => preferredOverride ?? _options.KmsAlgorithm ?? SignatureAlgorithms.Es256,
|
||||
_ => SignatureAlgorithms.Es256
|
||||
};
|
||||
|
||||
// If SM is explicitly requested via options and env gate is on, allow SM2.
|
||||
if (string.Equals(preferred, SignatureAlgorithms.Sm2, StringComparison.OrdinalIgnoreCase))
|
||||
ValidateAlgorithmGate(preferred);
|
||||
return preferred;
|
||||
}
|
||||
|
||||
private static void ValidateAlgorithmGate(string algorithm)
|
||||
{
|
||||
if (string.Equals(algorithm, SignatureAlgorithms.Dilithium3, StringComparison.OrdinalIgnoreCase) ||
|
||||
string.Equals(algorithm, SignatureAlgorithms.Falcon512, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
if (!string.Equals(Environment.GetEnvironmentVariable("PQ_SOFT_ALLOWED"), "1", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
throw new InvalidOperationException("PQ signing requested but PQ_SOFT_ALLOWED is not enabled.");
|
||||
}
|
||||
}
|
||||
|
||||
if (string.Equals(algorithm, SignatureAlgorithms.Sm2, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
if (!string.Equals(Environment.GetEnvironmentVariable("SM_SOFT_ALLOWED"), "1", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
throw new InvalidOperationException("SM2 signing requested but SM_SOFT_ALLOWED is not enabled.");
|
||||
}
|
||||
}
|
||||
|
||||
return preferred;
|
||||
}
|
||||
|
||||
private static IReadOnlyList<string> BuildCertificateChain(
|
||||
|
||||
@@ -24,6 +24,22 @@ public sealed class DsseSignerOptions
|
||||
/// </summary>
|
||||
public string? SmAlgorithm { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Optional secondary algorithm for dual-signing (e.g., PQ co-sign).
|
||||
/// When set, a second DSSE signature is added using the specified algorithm.
|
||||
/// </summary>
|
||||
public string? SecondaryAlgorithm { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Optional provider hint for the secondary signature.
|
||||
/// </summary>
|
||||
public string? SecondaryProvider { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Optional key identifier for the secondary signature. Falls back to the primary key id when null.
|
||||
/// </summary>
|
||||
public string? SecondaryKeyId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the default issuer for signing identity metadata.
|
||||
/// </summary>
|
||||
|
||||
@@ -0,0 +1,118 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using FluentAssertions;
|
||||
using Microsoft.Extensions.Logging.Abstractions;
|
||||
using Microsoft.Extensions.Options;
|
||||
using StellaOps.Cryptography;
|
||||
using StellaOps.Signer.Core;
|
||||
using StellaOps.Signer.Infrastructure.Signing;
|
||||
using Xunit;
|
||||
|
||||
namespace StellaOps.Signer.Tests.Signing;
|
||||
|
||||
public class DualSignTests
|
||||
{
|
||||
[Fact]
|
||||
public async Task AddsSecondarySignature_WhenConfigured()
|
||||
{
|
||||
Environment.SetEnvironmentVariable("PQ_SOFT_ALLOWED", "1");
|
||||
|
||||
var registry = new CryptoProviderRegistry(new ICryptoProvider[] { new StubProvider() });
|
||||
var resolver = new StubKeyResolver("primary-key", "stub");
|
||||
|
||||
var options = Options.Create(new DsseSignerOptions
|
||||
{
|
||||
KeylessAlgorithm = SignatureAlgorithms.Es256,
|
||||
SecondaryAlgorithm = SignatureAlgorithms.Falcon512,
|
||||
SecondaryProvider = "stub",
|
||||
SecondaryKeyId = "secondary-key"
|
||||
});
|
||||
|
||||
var signer = new CryptoDsseSigner(registry, resolver, options, NullLogger<CryptoDsseSigner>.Instance);
|
||||
|
||||
var request = new SigningRequest(
|
||||
Options: new SigningOptions(SigningMode.Keyless),
|
||||
Payload: Array.Empty<byte>(),
|
||||
Subjects: Array.Empty<SigningSubject>(),
|
||||
PredicateType: "demo");
|
||||
|
||||
var entitlement = new ProofOfEntitlementResult(true, "entitled", Array.Empty<string>());
|
||||
var caller = new CallerContext("tenant", "subject", "plan", "scanner-digest");
|
||||
|
||||
var bundle = await signer.SignAsync(request, entitlement, caller, CancellationToken.None);
|
||||
|
||||
bundle.Envelope.Signatures.Should().HaveCount(2);
|
||||
bundle.Envelope.Signatures[0].KeyId.Should().Be("primary-key");
|
||||
bundle.Envelope.Signatures[1].KeyId.Should().Be("secondary-key");
|
||||
}
|
||||
|
||||
private sealed class StubProvider : ICryptoProvider
|
||||
{
|
||||
public string Name => "stub";
|
||||
|
||||
public bool Supports(CryptoCapability capability, string algorithmId) =>
|
||||
capability == CryptoCapability.Signing;
|
||||
|
||||
public IPasswordHasher GetPasswordHasher(string algorithmId) => throw new NotSupportedException();
|
||||
|
||||
public ICryptoHasher GetHasher(string algorithmId) => throw new NotSupportedException();
|
||||
|
||||
public ICryptoSigner GetSigner(string algorithmId, CryptoKeyReference keyReference) =>
|
||||
new StubSigner(keyReference.KeyId, algorithmId);
|
||||
|
||||
public void UpsertSigningKey(CryptoSigningKey signingKey) { }
|
||||
|
||||
public bool RemoveSigningKey(string keyId) => true;
|
||||
|
||||
public IReadOnlyCollection<CryptoSigningKey> GetSigningKeys() => Array.Empty<CryptoSigningKey>();
|
||||
}
|
||||
|
||||
private sealed class StubSigner : ICryptoSigner
|
||||
{
|
||||
public StubSigner(string keyId, string algorithmId)
|
||||
{
|
||||
KeyId = keyId;
|
||||
AlgorithmId = algorithmId;
|
||||
}
|
||||
|
||||
public string KeyId { get; }
|
||||
public string AlgorithmId { get; }
|
||||
|
||||
public ValueTask<byte[]> SignAsync(ReadOnlyMemory<byte> data, CancellationToken cancellationToken = default)
|
||||
{
|
||||
var payload = System.Text.Encoding.UTF8.GetBytes($"{AlgorithmId}:{KeyId}");
|
||||
return ValueTask.FromResult(payload);
|
||||
}
|
||||
|
||||
public ValueTask<bool> VerifyAsync(ReadOnlyMemory<byte> data, ReadOnlyMemory<byte> signature, CancellationToken cancellationToken = default)
|
||||
=> ValueTask.FromResult(true);
|
||||
|
||||
public Microsoft.IdentityModel.Tokens.JsonWebKey ExportPublicJsonWebKey()
|
||||
=> new() { Kid = KeyId, Alg = AlgorithmId, Kty = "oct" };
|
||||
}
|
||||
|
||||
private sealed class StubKeyResolver : ISigningKeyResolver
|
||||
{
|
||||
private readonly string keyId;
|
||||
private readonly string provider;
|
||||
|
||||
public StubKeyResolver(string keyId, string provider)
|
||||
{
|
||||
this.keyId = keyId;
|
||||
this.provider = provider;
|
||||
}
|
||||
|
||||
public Task<SigningKeyResolution> ResolveKeyAsync(SigningMode mode, string tenant, CancellationToken cancellationToken)
|
||||
{
|
||||
return Task.FromResult(new SigningKeyResolution(
|
||||
keyId,
|
||||
provider,
|
||||
issuer: null,
|
||||
subject: null,
|
||||
expiresAtUtc: null,
|
||||
certificateChain: Array.Empty<string>()));
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user