Skip to content

Commit

Permalink
Dev (#38)
Browse files Browse the repository at this point in the history
+ Added `(Disposable)BouncyCastleEntropyMonitor`
+ Added `(Disposable)BouncyCastleBackupRng`
  • Loading branch information
nd1012 authored Jun 29, 2024
1 parent 6147c94 commit 83e0454
Show file tree
Hide file tree
Showing 5 changed files with 408 additions and 3 deletions.
95 changes: 95 additions & 0 deletions src/wan24-Crypto-BC/BouncyCastleBackupRng.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
using System.Collections.Frozen;

namespace wan24.Crypto.BC
{
/// <summary>
/// RNG which uses backup RNGs on error (not seedable!)
/// </summary>
public class BouncyCastleBackupRng : IBouncyCastleRng
{
/// <summary>
/// Constructor
/// </summary>
/// <param name="rngs">RNGs</param>
public BouncyCastleBackupRng(params IBouncyCastleRng[] rngs)
{
if (rngs.Length < 1) throw new ArgumentOutOfRangeException(nameof(rngs));
RNGs = rngs.ToFrozenSet();
}

/// <summary>
/// RNGs
/// </summary>
public FrozenSet<IBouncyCastleRng> RNGs { get; }

/// <inheritdoc/>
public void AddSeed(ReadOnlySpan<byte> seed) { }

/// <inheritdoc/>
public Task AddSeedAsync(ReadOnlyMemory<byte> seed, CancellationToken cancellationToken = default) => Task.CompletedTask;

/// <inheritdoc/>
public void AddSeedMaterial(byte[] seed) { }

/// <inheritdoc/>
public void AddSeedMaterial(ReadOnlySpan<byte> seed) { }

/// <inheritdoc/>
public void AddSeedMaterial(long seed) { }

/// <inheritdoc/>
public Span<byte> FillBytes(in Span<byte> buffer)
{
NextBytes(buffer);
return buffer;
}

/// <inheritdoc/>
public Task<Memory<byte>> FillBytesAsync(Memory<byte> buffer, CancellationToken cancellationToken = default)
{
NextBytes(buffer.Span);
return Task.FromResult(buffer);
}

/// <inheritdoc/>
public byte[] GetBytes(in int count)
{
if (count < 1) return [];
byte[] res = new byte[count];
NextBytes(res.AsSpan());
return res;
}

/// <inheritdoc/>
public Task<byte[]> GetBytesAsync(int count, CancellationToken cancellationToken = default)
{
if (count < 1) return Task.FromResult(Array.Empty<byte>());
byte[] res = new byte[count];
NextBytes(res.AsSpan());
return Task.FromResult(res);
}

/// <inheritdoc/>
public void NextBytes(byte[] bytes) => NextBytes(bytes.AsSpan());

/// <inheritdoc/>
public void NextBytes(byte[] bytes, int start, int len) => NextBytes(bytes.AsSpan(start, len));

/// <inheritdoc/>
public void NextBytes(Span<byte> bytes)
{
List<Exception> exceptions = [];
foreach (IBouncyCastleRng rng in RNGs)
try
{
rng.NextBytes(bytes);
return;
}
catch (Exception ex)
{
exceptions.Add(ex);
}
throw CryptographicException.From(new AggregateException("No RNG produced RND without an error", [.. exceptions]));
}
}
}
97 changes: 97 additions & 0 deletions src/wan24-Crypto-BC/BouncyCastleEntropyMonitor.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
namespace wan24.Crypto.BC
{
/// <summary>
/// Entropy monitoring RNG (uses <see cref="EntropyHelper.CheckEntropy(in ReadOnlySpan{byte}, EntropyHelper.Algorithms?, in bool)"/>; won't monitor seed)
/// </summary>
/// <remarks>
/// Constructor
/// </remarks>
/// <param name="rng">Entropy monitored RNG</param>
public class BouncyCastleEntropyMonitor(in IBouncyCastleRng rng) : IBouncyCastleRng
{
/// <summary>
/// Entropy monitored RNG
/// </summary>
public IBouncyCastleRng RNG { get; } = rng;

/// <summary>
/// Entropy algorithms to use
/// </summary>
public EntropyHelper.Algorithms? Algorithms { get; init; }

/// <summary>
/// Max. number of retries to get RND with the required entropy (zero for no limit)
/// </summary>
public int MaxRetries { get; init; }

/// <summary>
/// Min. RND length required for monitoring
/// </summary>
public int MinRndlength { get; init; }

/// <inheritdoc/>
public virtual void AddSeed(ReadOnlySpan<byte> seed) => RNG.AddSeed(seed);

/// <inheritdoc/>
public virtual Task AddSeedAsync(ReadOnlyMemory<byte> seed, CancellationToken cancellationToken = default) => RNG.AddSeedAsync(seed, cancellationToken);

/// <inheritdoc/>
public virtual void AddSeedMaterial(byte[] seed) => RNG.AddSeedMaterial(seed);

/// <inheritdoc/>
public virtual void AddSeedMaterial(ReadOnlySpan<byte> seed) => RNG.AddSeedMaterial(seed);

/// <inheritdoc/>
public virtual void AddSeedMaterial(long seed) => RNG.AddSeedMaterial(seed);

/// <inheritdoc/>
public Span<byte> FillBytes(in Span<byte> buffer)
{
NextBytes(buffer);
return buffer;
}

/// <inheritdoc/>
public Task<Memory<byte>> FillBytesAsync(Memory<byte> buffer, CancellationToken cancellationToken = default)
{
NextBytes(buffer.Span);
return Task.FromResult(buffer);
}

/// <inheritdoc/>
public byte[] GetBytes(in int count)
{
if (count < 1) return [];
byte[] res = new byte[count];
NextBytes(res.AsSpan());
return res;
}

/// <inheritdoc/>
public Task<byte[]> GetBytesAsync(int count, CancellationToken cancellationToken = default)
{
if (count < 1) return Task.FromResult(Array.Empty<byte>());
byte[] res = new byte[count];
NextBytes(res.AsSpan());
return Task.FromResult(res);
}

/// <inheritdoc/>
public void NextBytes(byte[] bytes) => NextBytes(bytes.AsSpan());

/// <inheritdoc/>
public void NextBytes(byte[] bytes, int start, int len) => NextBytes(bytes.AsSpan(start, len));

/// <inheritdoc/>
public void NextBytes(Span<byte> bytes)
{
if (bytes.Length < 1) return;
for (int i = 0, len = MaxRetries < 1 ? int.MaxValue : MaxRetries; i < len; i++)
{
RNG.NextBytes(bytes);
if (bytes.Length < MinRndlength || EntropyHelper.CheckEntropy(bytes, Algorithms)) return;
}
throw CryptographicException.From("Failed to get RND with the required entropy", new InvalidDataException());
}
}
}
105 changes: 105 additions & 0 deletions src/wan24-Crypto-BC/DisposableBouncyCastleBackupRng.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
using System.Collections.Frozen;
using wan24.Core;

namespace wan24.Crypto.BC
{
/// <summary>
/// RNG which uses backup RNGs on error (not seedable!)
/// </summary>
public class DisposableBouncyCastleBackupRng : DisposableBase, IBouncyCastleRng
{
/// <summary>
/// Constructor
/// </summary>
/// <param name="rngs">RNGs (will be disposed)</param>
public DisposableBouncyCastleBackupRng(params IBouncyCastleRng[] rngs) : base()
{
if (rngs.Length < 1) throw new ArgumentOutOfRangeException(nameof(rngs));
RNGs = rngs.ToFrozenSet();
}

/// <summary>
/// RNGs (will be disposed)
/// </summary>
public FrozenSet<IBouncyCastleRng> RNGs { get; }

/// <inheritdoc/>
public void AddSeed(ReadOnlySpan<byte> seed) { }

/// <inheritdoc/>
public Task AddSeedAsync(ReadOnlyMemory<byte> seed, CancellationToken cancellationToken = default) => Task.CompletedTask;

/// <inheritdoc/>
public void AddSeedMaterial(byte[] seed) { }

/// <inheritdoc/>
public void AddSeedMaterial(ReadOnlySpan<byte> seed) { }

/// <inheritdoc/>
public void AddSeedMaterial(long seed) { }

/// <inheritdoc/>
public Span<byte> FillBytes(in Span<byte> buffer)
{
NextBytes(buffer);
return buffer;
}

/// <inheritdoc/>
public Task<Memory<byte>> FillBytesAsync(Memory<byte> buffer, CancellationToken cancellationToken = default)
{
NextBytes(buffer.Span);
return Task.FromResult(buffer);
}

/// <inheritdoc/>
public byte[] GetBytes(in int count)
{
EnsureUndisposed();
if (count < 1) return [];
byte[] res = new byte[count];
NextBytes(res.AsSpan());
return res;
}

/// <inheritdoc/>
public Task<byte[]> GetBytesAsync(int count, CancellationToken cancellationToken = default)
{
EnsureUndisposed();
if (count < 1) return Task.FromResult(Array.Empty<byte>());
byte[] res = new byte[count];
NextBytes(res.AsSpan());
return Task.FromResult(res);
}

/// <inheritdoc/>
public void NextBytes(byte[] bytes) => NextBytes(bytes.AsSpan());

/// <inheritdoc/>
public void NextBytes(byte[] bytes, int start, int len) => NextBytes(bytes.AsSpan(start, len));

/// <inheritdoc/>
public void NextBytes(Span<byte> bytes)
{
EnsureUndisposed();
List<Exception> exceptions = [];
foreach (IBouncyCastleRng rng in RNGs)
try
{
rng.NextBytes(bytes);
return;
}
catch (Exception ex)
{
exceptions.Add(ex);
}
throw CryptographicException.From(new AggregateException("No RNG produced RND without an error", [.. exceptions]));
}

/// <inheritdoc/>
protected override void Dispose(bool disposing) => RNGs.TryDisposeAll();

/// <inheritdoc/>
protected override async Task DisposeCore() => await RNGs.TryDisposeAsync().DynamicContext();
}
}
Loading

0 comments on commit 83e0454

Please sign in to comment.