Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Change jws tool #6

Closed
wants to merge 8 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions src/ACMESharp/Authorizations/AuthorizationDecoder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ public class AuthorizationDecoder
/// https://tools.ietf.org/html/draft-ietf-acme-acme-12#section-8
/// </remarks>
public static IChallengeValidationDetails DecodeChallengeValidation(
Authorization authz, string challengeType, IJwsTool signer)
Authorization authz, string challengeType, JwsAlgorithm signer)
{
var challenge = authz.Challenges.Where(x => x.Type == challengeType)
.FirstOrDefault();
Expand Down Expand Up @@ -42,7 +42,7 @@ public static IChallengeValidationDetails DecodeChallengeValidation(
/// https://tools.ietf.org/html/draft-ietf-acme-acme-12#section-8.4
/// </remarks>
public static Dns01ChallengeValidationDetails ResolveChallengeForDns01(
Authorization authz, Challenge challenge, IJwsTool signer)
Authorization authz, Challenge challenge, JwsAlgorithm signer)
{
var keyAuthzDigested = JwsHelper.ComputeKeyAuthorizationDigest(
signer, challenge.Token);
Expand All @@ -62,7 +62,7 @@ public static Dns01ChallengeValidationDetails ResolveChallengeForDns01(
/// https://tools.ietf.org/html/draft-ietf-acme-acme-12#section-8.3
/// </remarks>
public static Http01ChallengeValidationDetails ResolveChallengeForHttp01(
Authorization authz, Challenge challenge, IJwsTool signer)
Authorization authz, Challenge challenge, JwsAlgorithm signer)
{
var keyAuthz = JwsHelper.ComputeKeyAuthorization(
signer, challenge.Token);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
using System;
using System;
using System.IO;

namespace ACMESharp.Crypto.JOSE
Expand All @@ -7,18 +7,16 @@ namespace ACMESharp.Crypto.JOSE
/// Defines the interface for a tool that provides the required
/// JOSE Web Signature (JWS) functions as used by the ACME protocol.
/// </summary>
public interface IJwsTool : IDisposable
public interface IJwsSigner : IDisposable
{
string JwsAlg
{ get; }

void Init();
string ExportAlgorithm();

string Export();
void Import(string privateJwk);

void Import(string exported);

object ExportJwk(bool canonical = false);
object ExportPublicJwk();

byte[] Sign(byte[] raw);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,48 +9,49 @@ namespace ACMESharp.Crypto.JOSE.Impl
/// JWS Signing tool implements ES-family of algorithms as per
/// http://self-issued.info/docs/draft-ietf-jose-json-web-algorithms-00.html#SigAlgTable
/// </summary>
public class ESJwsTool : IJwsTool
internal class ESJwsSigner : IJwsSigner
{
private HashAlgorithmName _shaName;
private ECDsa _dsa;

private object _jwk;

/// <summary>
/// Specifies the size in bits of the SHA-2 hash function to use.
/// Supported values are 256, 384 and 512.
/// </summary>
public int HashSize { get; set; } = 256;
private int HashSize { get; set; }

/// <summary>
/// Specifies the elliptic curve to use.
/// </summary>
/// <returns></returns>
public ECCurve Curve { get; private set; }
private ECCurve Curve { get; set; }

/// <summary>
/// As per: https://tools.ietf.org/html/rfc7518#section-6.2.1.1
/// </summary>
public string CurveName { get; private set; }
public string CurveName => $"P-{HashSize}";

public string JwsAlg => $"ES{HashSize}";

public void Init()
public ESJwsSigner(int hashSize)
{
HashSize = hashSize;

switch (HashSize)
{
case 256:
_shaName = HashAlgorithmName.SHA256;
Curve = ECCurve.NamedCurves.nistP256;
CurveName = "P-256";
break;
case 384:
_shaName = HashAlgorithmName.SHA384;
Curve = ECCurve.NamedCurves.nistP384;
CurveName = "P-384";
break;
case 512:
_shaName = HashAlgorithmName.SHA512;
Curve = ECCurve.NamedCurves.nistP521;
CurveName = "P-521";
break;
default:
throw new System.InvalidOperationException("illegal SHA2 hash size");
Expand All @@ -65,12 +66,11 @@ public void Dispose()
_dsa = null;
}

public string Export()
public string ExportAlgorithm()
{
var ecParams = _dsa.ExportParameters(true);
var details = new ExportDetails
{
HashSize = HashSize,
D = Convert.ToBase64String(ecParams.D),
X = Convert.ToBase64String(ecParams.Q.X),
Y = Convert.ToBase64String(ecParams.Q.Y),
Expand All @@ -83,8 +83,6 @@ public void Import(string exported)
// TODO: this is inefficient and corner cases exist that will break this -- FIX THIS!!!

var details = JsonConvert.DeserializeObject<ExportDetails>(exported);
HashSize = details.HashSize;
Init();

var ecParams = _dsa.ExportParameters(true);
ecParams.D = Convert.FromBase64String(details.D);
Expand All @@ -93,29 +91,9 @@ public void Import(string exported)
_dsa.ImportParameters(ecParams);

}

// public void Save(Stream stream)
// {
// using (var w = new StreamWriter(stream))
// {
// w.Write(_dsa.ToXmlString(true));
// }
// }

// public void Load(Stream stream)
// {
// using (var r = new StreamReader(stream))
// {
// _dsa.FromXmlString(r.ReadToEnd());
// }
// }


public object ExportJwk(bool canonical = false)

public object ExportPublicJwk()
{
// Note, we only produce a canonical form of the JWK
// for export therefore we ignore the canonical param

if (_jwk == null)
{
var keyParams = _dsa.ExportParameters(false);
Expand All @@ -133,16 +111,14 @@ public object ExportJwk(bool canonical = false)

return _jwk;
}

public byte[] Sign(byte[] raw)
{
return _dsa.SignData(raw, _shaName);
}

class ExportDetails
{
public int HashSize { get; set; }

public string D { get; set; }

public string X { get; set; }
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
using System;
using System;
using System.IO;
using System.Security.Cryptography;

Expand All @@ -8,17 +8,18 @@ namespace ACMESharp.Crypto.JOSE.Impl
/// JWS Signing tool implements RS-family of algorithms as per
/// http://self-issued.info/docs/draft-ietf-jose-json-web-algorithms-00.html#SigAlgTable
/// </summary>
public class RSJwsTool : IJwsTool
internal class RSJwsSigner : IJwsSigner
{
private HashAlgorithm _sha;
private RSACryptoServiceProvider _rsa;

private object _jwk;

/// <summary>
/// Specifies the size in bits of the SHA-2 hash function to use.
/// Supported values are 256, 384 and 512.
/// </summary>
public int HashSize { get; set; } = 256;
private int HashSize { get; set; }

/// <summary>
/// Specifies the size in bits of the RSA key to use.
Expand All @@ -29,8 +30,10 @@ public class RSJwsTool : IJwsTool

public string JwsAlg => $"RS{HashSize}";

public void Init()
public RSJwsSigner(int hashSize)
{
HashSize = hashSize;

switch (HashSize)
{
case 256:
Expand Down Expand Up @@ -60,7 +63,7 @@ public void Dispose()
_sha = null;
}

public string Export()
public string ExportAlgorithm()
{
return _rsa.ToXmlString(true);
}
Expand All @@ -69,28 +72,10 @@ public void Import(string exported)
{
_rsa.FromXmlString(exported);
}


// public void Save(Stream stream)
// {
// using (var w = new StreamWriter(stream))
// {
// w.Write(_rsa.ToXmlString(true));
// }
// }

// public void Load(Stream stream)
// {
// using (var r = new StreamReader(stream))
// {
// _rsa.FromXmlString(r.ReadToEnd());
// }
// }

public object ExportJwk(bool canonical = false)
public object ExportPublicJwk()
{
// Note, we only produce a canonical form of the JWK
// for export therefore we ignore the canonical param

if (_jwk == null)
{
var keyParams = _rsa.ExportParameters(false);
Expand Down
72 changes: 72 additions & 0 deletions src/ACMESharp/Crypto/JOSE/JWSAlgorithm.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
using System;

namespace ACMESharp.Crypto.JOSE
{
public class JwsAlgorithm
{
private IJwsSigner _jwsTool;

public JwsAlgorithm(string jwsAlgorithm)
{
if (!int.TryParse(jwsAlgorithm.Substring(2), out int size))
throw new ArgumentException("Could not parse Key or Hash size", nameof(jwsAlgorithm));

if (jwsAlgorithm.StartsWith("ES"))
{
var tool = new Impl.ESJwsSigner(size);

_jwsTool = tool;
}

if (jwsAlgorithm.StartsWith("RS"))
{
var tool = new Impl.RSJwsSigner(size);

_jwsTool = tool;
}

if (_jwsTool == null)
throw new InvalidOperationException("Unknown JwsAlgorithm");
}

public JwsAlgorithm(JwsAlgorithmExport exported)
:this(exported.Algorithm)
{
_jwsTool.Import(exported.Export);
}

public string JwsAlg => _jwsTool.JwsAlg;

public void Dispose()
{
_jwsTool.Dispose();
}

public JwsAlgorithmExport Export()
{
var export = new JwsAlgorithmExport
{
Algorithm = _jwsTool.JwsAlg,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wonder if should derive the Algorithm value from the original constructor argument instead of what the IJwsSigner is giving you. In this case the JwsAlg value is what is needed for a JWS payload to understand the contents and associated key type, but it may not exactly translate to the specific IJwsSigner implementation upon reload.

And along those same lines, the JwsAlgorithm(string) constructor would actually be taking an implementation identifier of sorts, not actually a JWS algorithm ID.

For example, in the current core code base, there are the 2 default JWS signer implementations, RSA and ECDSA, but I recently had to add a third one, an alternative ECDSA version because the 2 default implementations would not work in my target environment because they relied on the BCL crypto support which was missing, and I needed to add an alternative variation that was based on BouncyCastle. Even though it's a distinct implementation of it's own, it still implements the ES256 (and ES384 and ES521) algorithms which is what it returns in its JwsAlg property.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You are right - I reduced the extend-ability here, which should be there to swap out the actual JWS implementation. I was so fixated on making reuse of the key more "graspable" and straight forward, that I ignored that.
I have an idea, where you would get the possibility to define which JwsImplementation reacts on which name - something like a Dict<string, Func<string, IJwsSigner>> or something along those lines. Also instead of using the JwsAlg-Names i could use a more distinguished list like DefaultRSA374 or something like that.

Export = _jwsTool.ExportAlgorithm()
};

return export;
}

public object ExportPublicJwk()
{
return _jwsTool.ExportPublicJwk();
}

public byte[] Sign(string raw)
{
// For Base64 Strings, UTF8 and ASCII will yield the same results, so we can safely use UTF8
return Sign(System.Text.Encoding.UTF8.GetBytes(raw));
}

public byte[] Sign(byte[] raw)
{
return _jwsTool.Sign(raw);
}
}
}
10 changes: 10 additions & 0 deletions src/ACMESharp/Crypto/JOSE/JwsAlgorithmExport.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
namespace ACMESharp.Crypto.JOSE
{

public class JwsAlgorithmExport
{
public string Algorithm { get; set; }

public string Export { get; set; }
}
}
10 changes: 5 additions & 5 deletions src/ACMESharp/Crypto/JOSE/JwsHelper.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
using Newtonsoft.Json;
using Newtonsoft.Json;
using System;
using System.Security.Cryptography;
using System.Text;
Expand Down Expand Up @@ -99,12 +99,12 @@ public static string SignFlatJson(Func<byte[], byte[]> sigFunc, string payload,
/// as per <see href="https://tools.ietf.org/html/rfc7638">RFC 7638</see>,
/// JSON Web Key (JWK) Thumbprint.
/// </summary>
public static byte[] ComputeThumbprint(IJwsTool signer, HashAlgorithm algor)
public static byte[] ComputeThumbprint(JwsAlgorithm signer, HashAlgorithm algor)
{
// As per RFC 7638 Section 3, we export the JWK in a canonical form
// and then produce a JSON object with no whitespace or line breaks

var jwkCanon = signer.ExportJwk(true);
var jwkCanon = signer.ExportPublicJwk();
var jwkJson = JsonConvert.SerializeObject(jwkCanon, Formatting.None);
var jwkBytes = Encoding.UTF8.GetBytes(jwkJson);
var jwkHash = algor.ComputeHash(jwkBytes);
Expand All @@ -118,7 +118,7 @@ public static byte[] ComputeThumbprint(IJwsTool signer, HashAlgorithm algor)
/// <see href="https://tools.ietf.org/html/draft-ietf-acme-acme-01#section-7.1"
/// >ACME specification, section 7.1</see>.
/// </summary>
public static string ComputeKeyAuthorization(IJwsTool signer, string token)
public static string ComputeKeyAuthorization(JwsAlgorithm signer, string token)
{
using (var sha = SHA256.Create())
{
Expand All @@ -132,7 +132,7 @@ public static string ComputeKeyAuthorization(IJwsTool signer, string token)
/// >ACME Key Authorization</see> as required by some of the ACME Challenge
/// responses.
/// </summary>
public static string ComputeKeyAuthorizationDigest(IJwsTool signer, string token)
public static string ComputeKeyAuthorizationDigest(JwsAlgorithm signer, string token)
{
using (var sha = SHA256.Create())
{
Expand Down
Loading