sprints and audit work
This commit is contained in:
@@ -195,7 +195,15 @@ public sealed class KnowledgeSnapshotImporter : IKnowledgeSnapshotImporter
|
||||
{
|
||||
try
|
||||
{
|
||||
var filePath = Path.Combine(bundleDir, entry.RelativePath.Replace('/', Path.DirectorySeparatorChar));
|
||||
// Validate path to prevent traversal attacks
|
||||
if (!PathValidation.IsSafeRelativePath(entry.RelativePath))
|
||||
{
|
||||
result.Failed++;
|
||||
result.Errors.Add($"Unsafe path detected: {entry.RelativePath}");
|
||||
continue;
|
||||
}
|
||||
|
||||
var filePath = PathValidation.SafeCombine(bundleDir, entry.RelativePath);
|
||||
if (!File.Exists(filePath))
|
||||
{
|
||||
result.Failed++;
|
||||
@@ -250,7 +258,15 @@ public sealed class KnowledgeSnapshotImporter : IKnowledgeSnapshotImporter
|
||||
{
|
||||
try
|
||||
{
|
||||
var filePath = Path.Combine(bundleDir, entry.RelativePath.Replace('/', Path.DirectorySeparatorChar));
|
||||
// Validate path to prevent traversal attacks
|
||||
if (!PathValidation.IsSafeRelativePath(entry.RelativePath))
|
||||
{
|
||||
result.Failed++;
|
||||
result.Errors.Add($"Unsafe path detected: {entry.RelativePath}");
|
||||
continue;
|
||||
}
|
||||
|
||||
var filePath = PathValidation.SafeCombine(bundleDir, entry.RelativePath);
|
||||
if (!File.Exists(filePath))
|
||||
{
|
||||
result.Failed++;
|
||||
@@ -305,7 +321,15 @@ public sealed class KnowledgeSnapshotImporter : IKnowledgeSnapshotImporter
|
||||
{
|
||||
try
|
||||
{
|
||||
var filePath = Path.Combine(bundleDir, entry.RelativePath.Replace('/', Path.DirectorySeparatorChar));
|
||||
// Validate path to prevent traversal attacks
|
||||
if (!PathValidation.IsSafeRelativePath(entry.RelativePath))
|
||||
{
|
||||
result.Failed++;
|
||||
result.Errors.Add($"Unsafe path detected: {entry.RelativePath}");
|
||||
continue;
|
||||
}
|
||||
|
||||
var filePath = PathValidation.SafeCombine(bundleDir, entry.RelativePath);
|
||||
if (!File.Exists(filePath))
|
||||
{
|
||||
result.Failed++;
|
||||
@@ -349,9 +373,52 @@ public sealed class KnowledgeSnapshotImporter : IKnowledgeSnapshotImporter
|
||||
|
||||
private static async Task ExtractBundleAsync(string bundlePath, string targetDir, CancellationToken ct)
|
||||
{
|
||||
var normalizedTargetDir = Path.GetFullPath(targetDir);
|
||||
|
||||
await using var fileStream = File.OpenRead(bundlePath);
|
||||
await using var gzipStream = new GZipStream(fileStream, CompressionMode.Decompress);
|
||||
await TarFile.ExtractToDirectoryAsync(gzipStream, targetDir, overwriteFiles: true, ct);
|
||||
await using var tarReader = new TarReader(gzipStream, leaveOpen: false);
|
||||
|
||||
while (await tarReader.GetNextEntryAsync(copyData: true, ct) is { } entry)
|
||||
{
|
||||
if (string.IsNullOrEmpty(entry.Name))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
// Validate entry path to prevent traversal attacks
|
||||
if (!PathValidation.IsSafeRelativePath(entry.Name))
|
||||
{
|
||||
throw new InvalidOperationException($"Unsafe tar entry path detected: {entry.Name}");
|
||||
}
|
||||
|
||||
var destinationPath = Path.GetFullPath(Path.Combine(normalizedTargetDir, entry.Name));
|
||||
|
||||
// Verify the path is within the target directory
|
||||
if (!destinationPath.StartsWith(normalizedTargetDir, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
throw new InvalidOperationException($"Tar entry path escapes target directory: {entry.Name}");
|
||||
}
|
||||
|
||||
// Create directory if needed
|
||||
var entryDir = Path.GetDirectoryName(destinationPath);
|
||||
if (!string.IsNullOrEmpty(entryDir))
|
||||
{
|
||||
Directory.CreateDirectory(entryDir);
|
||||
}
|
||||
|
||||
// Extract based on entry type
|
||||
if (entry.EntryType == TarEntryType.Directory)
|
||||
{
|
||||
Directory.CreateDirectory(destinationPath);
|
||||
}
|
||||
else if (entry.EntryType == TarEntryType.RegularFile ||
|
||||
entry.EntryType == TarEntryType.V7RegularFile)
|
||||
{
|
||||
await entry.ExtractToFileAsync(destinationPath, overwrite: true, ct);
|
||||
}
|
||||
// Skip symbolic links and other special entry types for security
|
||||
}
|
||||
}
|
||||
|
||||
private sealed class ModuleImportResult
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
// Description: Signs snapshot manifests using DSSE format for integrity verification.
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
using System.Globalization;
|
||||
using System.Security.Cryptography;
|
||||
using System.Text;
|
||||
using System.Text.Json;
|
||||
@@ -196,8 +197,9 @@ public sealed class SnapshotManifestSigner : ISnapshotManifestSigner
|
||||
{
|
||||
var typeBytes = Encoding.UTF8.GetBytes(payloadType);
|
||||
var prefixBytes = Encoding.UTF8.GetBytes(PreAuthenticationEncodingPrefix);
|
||||
var typeLenStr = typeBytes.Length.ToString();
|
||||
var payloadLenStr = payload.Length.ToString();
|
||||
// Use InvariantCulture to ensure ASCII decimal digits per DSSE spec
|
||||
var typeLenStr = typeBytes.Length.ToString(CultureInfo.InvariantCulture);
|
||||
var payloadLenStr = payload.Length.ToString(CultureInfo.InvariantCulture);
|
||||
|
||||
var totalLen = prefixBytes.Length + 1 +
|
||||
typeLenStr.Length + 1 +
|
||||
|
||||
@@ -178,39 +178,15 @@ public sealed class TimeAnchorService : ITimeAnchorService
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
// Roughtime is a cryptographic time synchronization protocol
|
||||
// This is a placeholder implementation - full implementation would use a Roughtime client
|
||||
// Full implementation requires a Roughtime client library
|
||||
var serverUrl = request.Source?["roughtime:".Length..] ?? "roughtime.cloudflare.com:2003";
|
||||
|
||||
// For now, fallback to local with indication of intended source
|
||||
var anchorTime = _timeProvider.GetUtcNow();
|
||||
var anchorData = new RoughtimeAnchorData
|
||||
{
|
||||
Timestamp = anchorTime,
|
||||
Server = serverUrl,
|
||||
Midpoint = anchorTime.ToUnixTimeSeconds(),
|
||||
Radius = 1000000, // 1 second radius in microseconds
|
||||
Nonce = _guidProvider.NewGuid().ToString("N"),
|
||||
MerkleRoot = request.MerkleRoot
|
||||
};
|
||||
|
||||
var anchorJson = JsonSerializer.Serialize(anchorData, JsonOptions);
|
||||
var anchorBytes = Encoding.UTF8.GetBytes(anchorJson);
|
||||
var tokenDigest = $"sha256:{Convert.ToHexString(SHA256.HashData(anchorBytes)).ToLowerInvariant()}";
|
||||
|
||||
await Task.CompletedTask;
|
||||
|
||||
return new TimeAnchorResult
|
||||
{
|
||||
Success = true,
|
||||
Content = new TimeAnchorContent
|
||||
{
|
||||
AnchorTime = anchorTime,
|
||||
Source = $"roughtime:{serverUrl}",
|
||||
TokenDigest = tokenDigest
|
||||
},
|
||||
TokenBytes = anchorBytes,
|
||||
Warning = "Roughtime client not implemented; using simulated response"
|
||||
};
|
||||
// Per no-silent-stubs rule: unimplemented paths must fail explicitly
|
||||
return TimeAnchorResult.Failed(
|
||||
$"Roughtime time anchor source '{serverUrl}' is not implemented. " +
|
||||
"Use 'local' source or implement Roughtime client integration.");
|
||||
}
|
||||
|
||||
private async Task<TimeAnchorResult> CreateRfc3161AnchorAsync(
|
||||
@@ -218,37 +194,15 @@ public sealed class TimeAnchorService : ITimeAnchorService
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
// RFC 3161 is the Internet X.509 PKI Time-Stamp Protocol (TSP)
|
||||
// This is a placeholder implementation - full implementation would use a TSA client
|
||||
// Full implementation requires a TSA client library
|
||||
var tsaUrl = request.Source?["rfc3161:".Length..] ?? "http://timestamp.digicert.com";
|
||||
|
||||
var anchorTime = _timeProvider.GetUtcNow();
|
||||
var anchorData = new Rfc3161AnchorData
|
||||
{
|
||||
Timestamp = anchorTime,
|
||||
TsaUrl = tsaUrl,
|
||||
SerialNumber = _guidProvider.NewGuid().ToString("N"),
|
||||
PolicyOid = "2.16.840.1.114412.2.1", // DigiCert timestamp policy
|
||||
MerkleRoot = request.MerkleRoot
|
||||
};
|
||||
|
||||
var anchorJson = JsonSerializer.Serialize(anchorData, JsonOptions);
|
||||
var anchorBytes = Encoding.UTF8.GetBytes(anchorJson);
|
||||
var tokenDigest = $"sha256:{Convert.ToHexString(SHA256.HashData(anchorBytes)).ToLowerInvariant()}";
|
||||
|
||||
await Task.CompletedTask;
|
||||
|
||||
return new TimeAnchorResult
|
||||
{
|
||||
Success = true,
|
||||
Content = new TimeAnchorContent
|
||||
{
|
||||
AnchorTime = anchorTime,
|
||||
Source = $"rfc3161:{tsaUrl}",
|
||||
TokenDigest = tokenDigest
|
||||
},
|
||||
TokenBytes = anchorBytes,
|
||||
Warning = "RFC 3161 TSA client not implemented; using simulated response"
|
||||
};
|
||||
// Per no-silent-stubs rule: unimplemented paths must fail explicitly
|
||||
return TimeAnchorResult.Failed(
|
||||
$"RFC 3161 time anchor source '{tsaUrl}' is not implemented. " +
|
||||
"Use 'local' source or implement RFC 3161 TSA client integration.");
|
||||
}
|
||||
|
||||
private sealed record LocalAnchorData
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.DependencyInjection.Extensions;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using StellaOps.AirGap.Sync.Services;
|
||||
using StellaOps.AirGap.Sync.Stores;
|
||||
using StellaOps.AirGap.Sync.Transport;
|
||||
@@ -42,7 +43,8 @@ public static class AirGapSyncServiceCollectionExtensions
|
||||
{
|
||||
var timeProvider = sp.GetService<TimeProvider>() ?? TimeProvider.System;
|
||||
var stateStore = sp.GetRequiredService<IHlcStateStore>();
|
||||
return new HybridLogicalClock.HybridLogicalClock(timeProvider, nodeId, stateStore);
|
||||
var logger = sp.GetRequiredService<ILogger<HybridLogicalClock.HybridLogicalClock>>();
|
||||
return new HybridLogicalClock.HybridLogicalClock(timeProvider, nodeId, stateStore, logger);
|
||||
});
|
||||
|
||||
// Register deterministic GUID provider
|
||||
|
||||
Reference in New Issue
Block a user