This commit is contained in:
Vladimir Moushkov
2025-10-15 10:03:56 +03:00
parent ea8226120c
commit ea1106ce7c
276 changed files with 21674 additions and 934 deletions

View File

@@ -1,8 +1,10 @@
using System;
using System;
using System.Collections.Generic;
using System.IO;
using System.Security.Cryptography;
using System.Text;
using System.Text.Json;
using System.Text.Json.Serialization;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Extensions.DependencyInjection;
@@ -16,6 +18,7 @@ using StellaOps.Cli.Services;
using StellaOps.Cli.Services.Models;
using StellaOps.Cli.Telemetry;
using StellaOps.Cli.Tests.Testing;
using StellaOps.Cryptography;
namespace StellaOps.Cli.Tests.Commands;
@@ -208,6 +211,34 @@ public sealed class CommandHandlersTests
}
}
[Theory]
[InlineData(null)]
[InlineData("default")]
[InlineData("libsodium")]
public async Task HandleAuthRevokeVerifyAsync_VerifiesBundlesUsingProviderRegistry(string? providerHint)
{
var original = Environment.ExitCode;
using var tempDir = new TempDirectory();
try
{
var artifacts = await WriteRevocationArtifactsAsync(tempDir, providerHint);
await CommandHandlers.HandleAuthRevokeVerifyAsync(
artifacts.BundlePath,
artifacts.SignaturePath,
artifacts.KeyPath,
verbose: true,
cancellationToken: CancellationToken.None);
Assert.Equal(0, Environment.ExitCode);
}
finally
{
Environment.ExitCode = original;
}
}
[Fact]
public async Task HandleAuthStatusAsync_ReportsCachedToken()
{
@@ -360,6 +391,79 @@ public sealed class CommandHandlersTests
}
}
private static async Task<RevocationArtifactPaths> WriteRevocationArtifactsAsync(TempDirectory temp, string? providerHint)
{
var (bundleBytes, signature, keyPem) = await BuildRevocationArtifactsAsync(providerHint);
var bundlePath = Path.Combine(temp.Path, "revocation-bundle.json");
var signaturePath = Path.Combine(temp.Path, "revocation-bundle.json.jws");
var keyPath = Path.Combine(temp.Path, "revocation-key.pem");
await File.WriteAllBytesAsync(bundlePath, bundleBytes);
await File.WriteAllTextAsync(signaturePath, signature);
await File.WriteAllTextAsync(keyPath, keyPem);
return new RevocationArtifactPaths(bundlePath, signaturePath, keyPath);
}
private static async Task<(byte[] Bundle, string Signature, string KeyPem)> BuildRevocationArtifactsAsync(string? providerHint)
{
var bundleBytes = Encoding.UTF8.GetBytes("{\"revocations\":[]}");
using var ecdsa = ECDsa.Create(ECCurve.NamedCurves.nistP256);
var parameters = ecdsa.ExportParameters(includePrivateParameters: true);
var signingKey = new CryptoSigningKey(
new CryptoKeyReference("revocation-test"),
SignatureAlgorithms.Es256,
privateParameters: in parameters,
createdAt: DateTimeOffset.UtcNow);
var provider = new DefaultCryptoProvider();
provider.UpsertSigningKey(signingKey);
var signer = provider.GetSigner(SignatureAlgorithms.Es256, signingKey.Reference);
var header = new Dictionary<string, object>
{
["alg"] = SignatureAlgorithms.Es256,
["kid"] = signingKey.Reference.KeyId,
["typ"] = "application/vnd.stellaops.revocation-bundle+jws",
["b64"] = false,
["crit"] = new[] { "b64" }
};
if (!string.IsNullOrWhiteSpace(providerHint))
{
header["provider"] = providerHint;
}
var serializerOptions = new JsonSerializerOptions
{
PropertyNamingPolicy = null,
DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull
};
var headerJson = JsonSerializer.Serialize(header, serializerOptions);
var encodedHeader = Base64UrlEncoder.Encode(Encoding.UTF8.GetBytes(headerJson));
var signingInput = new byte[encodedHeader.Length + 1 + bundleBytes.Length];
var headerBytes = Encoding.ASCII.GetBytes(encodedHeader);
Buffer.BlockCopy(headerBytes, 0, signingInput, 0, headerBytes.Length);
signingInput[headerBytes.Length] = (byte)'.';
Buffer.BlockCopy(bundleBytes, 0, signingInput, headerBytes.Length + 1, bundleBytes.Length);
var signatureBytes = await signer.SignAsync(signingInput);
var encodedSignature = Base64UrlEncoder.Encode(signatureBytes);
var jws = string.Concat(encodedHeader, "..", encodedSignature);
var publicKeyBytes = ecdsa.ExportSubjectPublicKeyInfo();
var keyPem = new string(PemEncoding.Write("PUBLIC KEY", publicKeyBytes));
return (bundleBytes, jws, keyPem);
}
private sealed record RevocationArtifactPaths(string BundlePath, string SignaturePath, string KeyPath);
private static IServiceProvider BuildServiceProvider(
IBackendOperationsClient backend,
IScannerExecutor? executor = null,