release orchestrator v1 draft and build fixes
This commit is contained in:
@@ -0,0 +1,342 @@
|
||||
namespace StellaOps.Cryptography.Plugin.Gost;
|
||||
|
||||
using Org.BouncyCastle.Crypto;
|
||||
using Org.BouncyCastle.Crypto.Digests;
|
||||
using Org.BouncyCastle.Crypto.Engines;
|
||||
using Org.BouncyCastle.Crypto.Generators;
|
||||
using Org.BouncyCastle.Crypto.Parameters;
|
||||
using Org.BouncyCastle.Crypto.Signers;
|
||||
using Org.BouncyCastle.Math;
|
||||
using Org.BouncyCastle.Math.EC;
|
||||
using Org.BouncyCastle.Security;
|
||||
using StellaOps.Plugin.Abstractions;
|
||||
using StellaOps.Plugin.Abstractions.Capabilities;
|
||||
using StellaOps.Plugin.Abstractions.Context;
|
||||
using StellaOps.Plugin.Abstractions.Health;
|
||||
using StellaOps.Plugin.Abstractions.Lifecycle;
|
||||
|
||||
/// <summary>
|
||||
/// GOST cryptography plugin providing Russian Federal cryptographic standards.
|
||||
/// Implements GOST R 34.10-2012 (signatures) and GOST R 34.11-2012 (hashes).
|
||||
/// </summary>
|
||||
public sealed class GostPlugin : CryptoPluginBase
|
||||
{
|
||||
private GostOptions? _options;
|
||||
private AsymmetricCipherKeyPair? _keyPair;
|
||||
private readonly SecureRandom _random = new();
|
||||
|
||||
/// <inheritdoc />
|
||||
public override PluginInfo Info => new(
|
||||
Id: "com.stellaops.crypto.gost",
|
||||
Name: "GOST Cryptography Provider",
|
||||
Version: "1.0.0",
|
||||
Vendor: "Stella Ops",
|
||||
Description: "Russian GOST R 34.10-2012 and R 34.11-2012 cryptographic algorithms",
|
||||
LicenseId: "AGPL-3.0-or-later");
|
||||
|
||||
/// <inheritdoc />
|
||||
public override IReadOnlyList<string> SupportedAlgorithms => new[]
|
||||
{
|
||||
"GOST-R34.10-2012-256",
|
||||
"GOST-R34.10-2012-512",
|
||||
"GOST-R34.11-2012-256",
|
||||
"GOST-R34.11-2012-512",
|
||||
"GOST-28147-89"
|
||||
};
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override Task InitializeCryptoServiceAsync(IPluginContext context, CancellationToken ct)
|
||||
{
|
||||
_options = context.Configuration.Bind<GostOptions>() ?? new GostOptions();
|
||||
|
||||
// Generate or load key pair if configured
|
||||
if (!string.IsNullOrEmpty(_options.PrivateKeyBase64))
|
||||
{
|
||||
// Load existing key - implementation depends on key format
|
||||
Context?.Logger.Info("GOST provider initialized with configured key");
|
||||
}
|
||||
else if (_options.GenerateKeyOnInit)
|
||||
{
|
||||
// Generate new GOST-R34.10-2012-256 key pair
|
||||
_keyPair = GenerateGost2012KeyPair(_options.KeySize);
|
||||
Context?.Logger.Info("GOST provider initialized with generated {KeySize}-bit key", _options.KeySize);
|
||||
}
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override bool CanHandle(CryptoOperation operation, string algorithm)
|
||||
{
|
||||
return algorithm.StartsWith("GOST", StringComparison.OrdinalIgnoreCase) &&
|
||||
SupportedAlgorithms.Contains(algorithm, StringComparer.OrdinalIgnoreCase);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override Task<byte[]> SignAsync(ReadOnlyMemory<byte> data, CryptoSignOptions options, CancellationToken ct)
|
||||
{
|
||||
EnsureActive();
|
||||
ct.ThrowIfCancellationRequested();
|
||||
|
||||
if (_keyPair == null)
|
||||
{
|
||||
throw new InvalidOperationException("No signing key available. Configure a key or enable GenerateKeyOnInit.");
|
||||
}
|
||||
|
||||
var algorithm = options.Algorithm;
|
||||
var digestBits = algorithm.Contains("512") ? 512 : 256;
|
||||
|
||||
// Create GOST R 34.11-2012 digest
|
||||
var digest = CreateGost2012Digest(digestBits);
|
||||
|
||||
// Create GOST R 34.10-2012 signer
|
||||
var signer = new ECGost3410Signer();
|
||||
signer.Init(true, _keyPair.Private);
|
||||
|
||||
// Hash the data
|
||||
digest.BlockUpdate(data.Span.ToArray(), 0, data.Length);
|
||||
var hash = new byte[digest.GetDigestSize()];
|
||||
digest.DoFinal(hash, 0);
|
||||
|
||||
// Sign the hash
|
||||
var signature = signer.GenerateSignature(hash);
|
||||
var sigBytes = EncodeSignature(signature);
|
||||
|
||||
Context?.Logger.Debug("Signed {DataLength} bytes with {Algorithm}", data.Length, algorithm);
|
||||
|
||||
return Task.FromResult(sigBytes);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override Task<bool> VerifyAsync(ReadOnlyMemory<byte> data, ReadOnlyMemory<byte> signature, CryptoVerifyOptions options, CancellationToken ct)
|
||||
{
|
||||
EnsureActive();
|
||||
ct.ThrowIfCancellationRequested();
|
||||
|
||||
if (_keyPair == null)
|
||||
{
|
||||
throw new InvalidOperationException("No verification key available.");
|
||||
}
|
||||
|
||||
var algorithm = options.Algorithm;
|
||||
var digestBits = algorithm.Contains("512") ? 512 : 256;
|
||||
|
||||
// Create GOST R 34.11-2012 digest
|
||||
var digest = CreateGost2012Digest(digestBits);
|
||||
|
||||
// Create GOST R 34.10-2012 verifier
|
||||
var verifier = new ECGost3410Signer();
|
||||
verifier.Init(false, _keyPair.Public);
|
||||
|
||||
// Hash the data
|
||||
digest.BlockUpdate(data.Span.ToArray(), 0, data.Length);
|
||||
var hash = new byte[digest.GetDigestSize()];
|
||||
digest.DoFinal(hash, 0);
|
||||
|
||||
// Decode and verify signature
|
||||
var sigComponents = DecodeSignature(signature.ToArray());
|
||||
var isValid = verifier.VerifySignature(hash, sigComponents[0], sigComponents[1]);
|
||||
|
||||
Context?.Logger.Debug("Verified signature: {IsValid}", isValid);
|
||||
|
||||
return Task.FromResult(isValid);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override Task<byte[]> EncryptAsync(ReadOnlyMemory<byte> data, CryptoEncryptOptions options, CancellationToken ct)
|
||||
{
|
||||
EnsureActive();
|
||||
ct.ThrowIfCancellationRequested();
|
||||
|
||||
if (!options.Algorithm.Contains("28147", StringComparison.Ordinal))
|
||||
{
|
||||
throw new NotSupportedException($"Encryption algorithm {options.Algorithm} not supported. Use GOST-28147-89.");
|
||||
}
|
||||
|
||||
// GOST 28147-89 block cipher encryption
|
||||
var engine = new Gost28147Engine();
|
||||
var keyBytes = GetEncryptionKey(options.KeyId);
|
||||
engine.Init(true, new KeyParameter(keyBytes));
|
||||
|
||||
var encrypted = ProcessBlocks(engine, data.ToArray());
|
||||
|
||||
Context?.Logger.Debug("Encrypted {DataLength} bytes with GOST-28147-89", data.Length);
|
||||
|
||||
return Task.FromResult(encrypted);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override Task<byte[]> DecryptAsync(ReadOnlyMemory<byte> data, CryptoDecryptOptions options, CancellationToken ct)
|
||||
{
|
||||
EnsureActive();
|
||||
ct.ThrowIfCancellationRequested();
|
||||
|
||||
// GOST 28147-89 block cipher decryption
|
||||
var engine = new Gost28147Engine();
|
||||
var keyBytes = GetEncryptionKey(options.KeyId);
|
||||
engine.Init(false, new KeyParameter(keyBytes));
|
||||
|
||||
var decrypted = ProcessBlocks(engine, data.ToArray());
|
||||
|
||||
Context?.Logger.Debug("Decrypted {DataLength} bytes with GOST-28147-89", data.Length);
|
||||
|
||||
return Task.FromResult(decrypted);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override Task<byte[]> HashAsync(ReadOnlyMemory<byte> data, string algorithm, CancellationToken ct)
|
||||
{
|
||||
EnsureActive();
|
||||
ct.ThrowIfCancellationRequested();
|
||||
|
||||
var digestBits = algorithm.Contains("512") ? 512 : 256;
|
||||
var digest = CreateGost2012Digest(digestBits);
|
||||
|
||||
digest.BlockUpdate(data.Span.ToArray(), 0, data.Length);
|
||||
var hash = new byte[digest.GetDigestSize()];
|
||||
digest.DoFinal(hash, 0);
|
||||
|
||||
Context?.Logger.Debug("Computed {Algorithm} hash of {DataLength} bytes", algorithm, data.Length);
|
||||
|
||||
return Task.FromResult(hash);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override ValueTask DisposeAsync()
|
||||
{
|
||||
_keyPair = null;
|
||||
State = PluginLifecycleState.Stopped;
|
||||
return ValueTask.CompletedTask;
|
||||
}
|
||||
|
||||
private static IDigest CreateGost2012Digest(int bits)
|
||||
{
|
||||
return bits switch
|
||||
{
|
||||
256 => new Gost3411_2012_256Digest(),
|
||||
512 => new Gost3411_2012_512Digest(),
|
||||
_ => throw new ArgumentException($"Unsupported digest size: {bits}")
|
||||
};
|
||||
}
|
||||
|
||||
private AsymmetricCipherKeyPair GenerateGost2012KeyPair(int keySize)
|
||||
{
|
||||
// GOST R 34.10-2012 uses specific elliptic curve parameters
|
||||
// For 256-bit: id-tc26-gost-3410-2012-256-paramSetA
|
||||
// For 512-bit: id-tc26-gost-3410-2012-512-paramSetA
|
||||
|
||||
var generator = new ECKeyPairGenerator("ECGOST3410");
|
||||
var domainParams = GetGost2012DomainParameters(keySize);
|
||||
var keyGenParams = new ECKeyGenerationParameters(domainParams, _random);
|
||||
generator.Init(keyGenParams);
|
||||
|
||||
return generator.GenerateKeyPair();
|
||||
}
|
||||
|
||||
private static ECDomainParameters GetGost2012DomainParameters(int keySize)
|
||||
{
|
||||
// Simplified: use predefined GOST parameters
|
||||
// In production, load from OID: 1.2.643.7.1.2.1.1.1 (256-bit) or 1.2.643.7.1.2.1.2.1 (512-bit)
|
||||
|
||||
if (keySize == 256)
|
||||
{
|
||||
// id-tc26-gost-3410-2012-256-paramSetA
|
||||
var p = new BigInteger("FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFD97", 16);
|
||||
var a = new BigInteger("FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFD94", 16);
|
||||
var b = new BigInteger("00000000000000000000000000000000000000000000000000000000000000A6", 16);
|
||||
var n = new BigInteger("FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF6C611070995AD10045841B09B761B893", 16);
|
||||
var h = BigInteger.One;
|
||||
var gx = new BigInteger("0000000000000000000000000000000000000000000000000000000000000001", 16);
|
||||
var gy = new BigInteger("8D91E471E0989CDA27DF505A453F2B7635294F2DDF23E3B122ACC99C9E9F1E14", 16);
|
||||
|
||||
var curve = new FpCurve(p, a, b, n, h);
|
||||
var g = curve.CreatePoint(gx, gy);
|
||||
return new ECDomainParameters(curve, g, n, h);
|
||||
}
|
||||
else
|
||||
{
|
||||
// id-tc26-gost-3410-2012-512-paramSetA (simplified)
|
||||
throw new NotImplementedException("512-bit GOST parameters not implemented in this example");
|
||||
}
|
||||
}
|
||||
|
||||
private static byte[] EncodeSignature(BigInteger[] signature)
|
||||
{
|
||||
// Encode r and s as fixed-length byte arrays concatenated
|
||||
var r = signature[0].ToByteArrayUnsigned();
|
||||
var s = signature[1].ToByteArrayUnsigned();
|
||||
|
||||
// Pad to 32 bytes each for 256-bit
|
||||
var encoded = new byte[64];
|
||||
Array.Copy(r, 0, encoded, 32 - r.Length, r.Length);
|
||||
Array.Copy(s, 0, encoded, 64 - s.Length, s.Length);
|
||||
|
||||
return encoded;
|
||||
}
|
||||
|
||||
private static BigInteger[] DecodeSignature(byte[] signature)
|
||||
{
|
||||
var r = new BigInteger(1, signature.Take(32).ToArray());
|
||||
var s = new BigInteger(1, signature.Skip(32).Take(32).ToArray());
|
||||
return new[] { r, s };
|
||||
}
|
||||
|
||||
private byte[] GetEncryptionKey(string keyId)
|
||||
{
|
||||
// In production, retrieve from secure key store
|
||||
// For now, derive a key from the key ID
|
||||
var digest = new Gost3411_2012_256Digest();
|
||||
var keyIdBytes = System.Text.Encoding.UTF8.GetBytes(keyId);
|
||||
digest.BlockUpdate(keyIdBytes, 0, keyIdBytes.Length);
|
||||
var key = new byte[32];
|
||||
digest.DoFinal(key, 0);
|
||||
return key;
|
||||
}
|
||||
|
||||
private static byte[] ProcessBlocks(IBlockCipher engine, byte[] data)
|
||||
{
|
||||
var blockSize = engine.GetBlockSize();
|
||||
var paddedLength = ((data.Length + blockSize - 1) / blockSize) * blockSize;
|
||||
var padded = new byte[paddedLength];
|
||||
Array.Copy(data, padded, data.Length);
|
||||
|
||||
var output = new byte[paddedLength];
|
||||
for (var i = 0; i < paddedLength; i += blockSize)
|
||||
{
|
||||
engine.ProcessBlock(padded, i, output, i);
|
||||
}
|
||||
|
||||
return output;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Configuration options for GOST cryptography plugin.
|
||||
/// </summary>
|
||||
public sealed class GostOptions
|
||||
{
|
||||
/// <summary>
|
||||
/// Path to key store file.
|
||||
/// </summary>
|
||||
public string? KeyStorePath { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Default key identifier for signing operations.
|
||||
/// </summary>
|
||||
public string? DefaultKeyId { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Base64-encoded private key (if not using key store).
|
||||
/// </summary>
|
||||
public string? PrivateKeyBase64 { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Generate a new key pair on initialization if no key is configured.
|
||||
/// </summary>
|
||||
public bool GenerateKeyOnInit { get; init; } = true;
|
||||
|
||||
/// <summary>
|
||||
/// Key size in bits (256 or 512).
|
||||
/// </summary>
|
||||
public int KeySize { get; init; } = 256;
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net10.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
<LangVersion>preview</LangVersion>
|
||||
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="BouncyCastle.Cryptography" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\StellaOps.Cryptography.Plugin\StellaOps.Cryptography.Plugin.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<None Include="plugin.yaml" CopyToOutputDirectory="PreserveNewest" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
@@ -0,0 +1,44 @@
|
||||
plugin:
|
||||
id: com.stellaops.crypto.gost
|
||||
name: GOST Cryptography Provider
|
||||
version: 1.0.0
|
||||
vendor: Stella Ops
|
||||
description: Russian GOST R 34.10-2012 and R 34.11-2012 cryptographic algorithms
|
||||
license: AGPL-3.0-or-later
|
||||
|
||||
entryPoint: StellaOps.Cryptography.Plugin.Gost.GostPlugin
|
||||
|
||||
minPlatformVersion: 1.0.0
|
||||
|
||||
capabilities:
|
||||
- type: crypto
|
||||
id: gost
|
||||
algorithms:
|
||||
- GOST-R34.10-2012-256
|
||||
- GOST-R34.10-2012-512
|
||||
- GOST-R34.11-2012-256
|
||||
- GOST-R34.11-2012-512
|
||||
- GOST-28147-89
|
||||
|
||||
configSchema:
|
||||
type: object
|
||||
properties:
|
||||
keyStorePath:
|
||||
type: string
|
||||
description: Path to GOST key store
|
||||
defaultKeyId:
|
||||
type: string
|
||||
description: Default key identifier for signing
|
||||
privateKeyBase64:
|
||||
type: string
|
||||
description: Base64-encoded private key
|
||||
generateKeyOnInit:
|
||||
type: boolean
|
||||
default: true
|
||||
description: Generate new key pair on initialization if no key configured
|
||||
keySize:
|
||||
type: integer
|
||||
enum: [256, 512]
|
||||
default: 256
|
||||
description: Key size in bits
|
||||
required: []
|
||||
Reference in New Issue
Block a user