/JwsDetachedStreaming

Streaming sign and verify detached JSON Web Signature

Primary LanguageC#MIT LicenseMIT

NuGet version (JwsDetachedStreaming) FuGet version (JwsDetachedStreaming)

JwsDetachedStreaming

Streaming sign and verify detached JSON Web Signature

Example

using var payload = File.OpenRead("payload.test");// payload as memory stream

var header = new JObject {{"custom", "value"}};

await using var ms = new MemoryStream();

await using var writer = await JwsDetachedWriter.CreateAsync(ms, header, "PS256", new SignerFactory());
payload.Position = 0;
await payload.CopyToAsync(writer.Payload);
await writer.Finish();

var jwsDetached = Encoding.ASCII.GetString(ms.ToArray());
            
await using var reader = await JwsDetachedReader.CreateAsync(ms.ToArray().AsMemory(), new VerifierFactory());
payload.Position = 0;
await payload.CopyToAsync(reader.Payload);

var readedHeader = await reader.ReadAsync();

Signer and Verifier implementation for example

class SignerFactory : ISignerFactory
{
    private readonly X509Certificate2 _certificate;

    public SignerFactory()
    {
        _certificate = new X509Certificate2("Provision/cert.pfx.test", "123456");
    }

    public Signer Create(JObject header)
    {
        return header.GetValue("alg").ToString() switch
        {
            "PS256" => new SignerPs256(_certificate),
            _ => throw new NotSupportedException("Signature algorithm not supported")
        };
    }
}

class SignerPs256 : Signer
{
    private readonly HashAlgorithm _hashAlgorithm = SHA256.Create();

    private readonly X509Certificate2 _certificate;

    public SignerPs256(X509Certificate2 certificate)
    {
        _certificate = certificate;
    }

    public override ValueTask WriteInputAsync(ReadOnlyMemory<byte> buffer, CancellationToken cancellationToken = default)
    {
        if (MemoryMarshal.TryGetArray(buffer, out ArraySegment<byte> array))
        {
            _hashAlgorithm.TransformBlock(array.Array!, array.Offset, array.Count, null, 0);
        }
        else
        {
            byte[] sharedBuffer = ArrayPool<byte>.Shared.Rent(buffer.Length);
            buffer.Span.CopyTo(sharedBuffer);
            _hashAlgorithm.TransformBlock(sharedBuffer, 0, buffer.Length, null, 0);
        }

        return new ValueTask();
    }

    public override Task<byte[]> GetSignatureAsync(CancellationToken cancellationToken = default)
    {
        _hashAlgorithm.TransformFinalBlock(Array.Empty<byte>(), 0, 0);

        var hash = _hashAlgorithm.Hash;

        using var privateKey = _certificate.GetRSAPrivateKey();
        return Task.FromResult(privateKey.SignHash(hash, HashAlgorithmName.SHA256, RSASignaturePadding.Pss));
    }

    protected override void Dispose(bool disposing)
    {
        _hashAlgorithm.Dispose();
    }
}

public class VerifierFactory : IVerifierFactory
{
    private readonly X509Certificate2 _certificate;

    public VerifierFactory()
    {
        _certificate = new X509Certificate2("Provision/cert.pfx.test", "123456");
    }

    public Verifier Create(JObject header)
    {
        return header.GetValue("alg").ToString() switch
        {
            "PS256" => new VerifierPs256(_certificate),
            _ => throw new NotSupportedException("Signature algorithm not supported")
        };
    }
}

class VerifierPs256 : Verifier
{
    private readonly HashAlgorithm _hashAlgorithm = SHA256.Create();

    private readonly X509Certificate2 _certificate;

    public VerifierPs256(X509Certificate2 certificate)
    {
        _certificate = certificate;
    }

    public override ValueTask WriteInputAsync(ReadOnlyMemory<byte> buffer, CancellationToken cancellationToken = default)
    {
        if (MemoryMarshal.TryGetArray(buffer, out ArraySegment<byte> array))
        {
            _hashAlgorithm.TransformBlock(array.Array!, array.Offset, array.Count, null, 0);
        }
        else
        {
            byte[] sharedBuffer = ArrayPool<byte>.Shared.Rent(buffer.Length);
            buffer.Span.CopyTo(sharedBuffer);
            _hashAlgorithm.TransformBlock(sharedBuffer, 0, buffer.Length, null, 0);
        }

        return new ValueTask();
    }

    public override Task<bool> VerifyAsync(ReadOnlyMemory<byte> signature, CancellationToken cancellationToken = default)
    {
        _hashAlgorithm.TransformFinalBlock(Array.Empty<byte>(), 0, 0);

        var hash = _hashAlgorithm.Hash;

        using var privateKey = _certificate.GetRSAPrivateKey();
        return Task.FromResult(privateKey.VerifyHash(hash, signature.Span, HashAlgorithmName.SHA256, RSASignaturePadding.Pss));
    }

    protected override void Dispose(bool disposing)
    {
        _hashAlgorithm.Dispose();
    }
}