diff --git a/src/ACMESharp/Authorizations/AuthorizationDecoder.cs b/src/ACMESharp/Authorizations/AuthorizationDecoder.cs
index b672558..603b91c 100644
--- a/src/ACMESharp/Authorizations/AuthorizationDecoder.cs
+++ b/src/ACMESharp/Authorizations/AuthorizationDecoder.cs
@@ -26,10 +26,12 @@ public static IChallengeValidationDetails DecodeChallengeValidation(
switch (challengeType)
{
- case "dns-01":
+ case Dns01ChallengeValidationDetails.Dns01ChallengeType:
return ResolveChallengeForDns01(authz, challenge, signer);
- case "http-01":
+ case Http01ChallengeValidationDetails.Http01ChallengeType:
return ResolveChallengeForHttp01(authz, challenge, signer);
+ case TlsAlpn01ChallengeValidationDetails.TlsAlpn01ChallengeType:
+ return ResolveChallengeForTlsAlpn01(authz, challenge, signer);
}
throw new NotImplementedException(
@@ -78,5 +80,20 @@ public static Http01ChallengeValidationDetails ResolveChallengeForHttp01(
HttpResourceValue = keyAuthz,
};
}
+
+ ///
+ ///
+ ///
+ /// https://tools.ietf.org/html/draft-ietf-acme-tls-alpn-05
+ ///
+ public static TlsAlpn01ChallengeValidationDetails ResolveChallengeForTlsAlpn01(
+ Authorization authz, Challenge challenge, IJwsTool signer)
+ {
+ var keyAuthz = JwsHelper.ComputeKeyAuthorization(signer, challenge.Token);
+ return new TlsAlpn01ChallengeValidationDetails
+ {
+ TokenValue = keyAuthz,
+ };
+ }
}
}
\ No newline at end of file
diff --git a/src/ACMESharp/Authorizations/TlsAlpn01ChallengeValidationDetails.cs b/src/ACMESharp/Authorizations/TlsAlpn01ChallengeValidationDetails.cs
new file mode 100644
index 0000000..acad99e
--- /dev/null
+++ b/src/ACMESharp/Authorizations/TlsAlpn01ChallengeValidationDetails.cs
@@ -0,0 +1,16 @@
+namespace ACMESharp.Authorizations
+{
+ ///
+ /// https://tools.ietf.org/html/draft-ietf-acme-tls-alpn-05
+ ///
+ public class TlsAlpn01ChallengeValidationDetails : IChallengeValidationDetails
+ {
+ public const string TlsAlpn01ChallengeType = "tls-alpn-01";
+ public const string AlpnExtensionName = "acme-tls/1";
+ public const string AcmeIdentifierExtension = "acmeIdentifier";
+
+ public string ChallengeType => TlsAlpn01ChallengeType;
+
+ public string TokenValue { get; set; }
+ }
+}
\ No newline at end of file
diff --git a/src/ACMESharp/Crypto/JOSE/Impl/ESJwsTool.cs b/src/ACMESharp/Crypto/JOSE/Impl/ESJwsTool.cs
index 61265f2..07dbe89 100644
--- a/src/ACMESharp/Crypto/JOSE/Impl/ESJwsTool.cs
+++ b/src/ACMESharp/Crypto/JOSE/Impl/ESJwsTool.cs
@@ -13,7 +13,7 @@ public class ESJwsTool : IJwsTool
{
private HashAlgorithmName _shaName;
private ECDsa _dsa;
- private object _jwk;
+ private ESJwk _jwk;
///
/// Specifies the size in bits of the SHA-2 hash function to use.
@@ -110,6 +110,22 @@ public void Import(string exported)
// }
// }
+ // As per RFC 7638 Section 3, these are the *required* elements of the
+ // JWK and are sorted in lexicographic order to produce a canonical form
+ class ESJwk
+ {
+ [JsonProperty(Order = 1)]
+ public string crv;
+
+ [JsonProperty(Order = 2)]
+ public string kty = "EC";
+
+ [JsonProperty(Order = 3)]
+ public string x;
+
+ [JsonProperty(Order = 4)]
+ public string y;
+ }
public object ExportJwk(bool canonical = false)
{
@@ -119,13 +135,9 @@ public object ExportJwk(bool canonical = false)
if (_jwk == null)
{
var keyParams = _dsa.ExportParameters(false);
- _jwk = new
+ _jwk = new ESJwk
{
- // As per RFC 7638 Section 3, these are the *required* elements of the
- // JWK and are sorted in lexicographic order to produce a canonical form
-
crv = CurveName,
- kty = "EC", // https://tools.ietf.org/html/rfc7518#section-6.2
x = CryptoHelper.Base64.UrlEncode(keyParams.Q.X),
y = CryptoHelper.Base64.UrlEncode(keyParams.Q.Y),
};
diff --git a/src/ACMESharp/Crypto/JOSE/Impl/RSJwsTool.cs b/src/ACMESharp/Crypto/JOSE/Impl/RSJwsTool.cs
index 856f00d..57ca334 100644
--- a/src/ACMESharp/Crypto/JOSE/Impl/RSJwsTool.cs
+++ b/src/ACMESharp/Crypto/JOSE/Impl/RSJwsTool.cs
@@ -1,4 +1,4 @@
-using System;
+using System;
using System.IO;
using System.Security.Cryptography;
using Newtonsoft.Json;
@@ -13,7 +13,7 @@ public class RSJwsTool : IJwsTool
{
private HashAlgorithm _sha;
private RSACryptoServiceProvider _rsa;
- private object _jwk;
+ private RSJwk _jwk;
///
/// Specifies the size in bits of the SHA-2 hash function to use.
@@ -87,6 +87,20 @@ public void Import(string exported)
// }
// }
+ // As per RFC 7638 Section 3, these are the *required* elements of the
+ // JWK and are sorted in lexicographic order to produce a canonical form
+ class RSJwk
+ {
+ [JsonProperty(Order = 1)]
+ public string e;
+
+ [JsonProperty(Order = 2)]
+ public string kty = "RSA";
+
+ [JsonProperty(Order = 3)]
+ public string n;
+ }
+
public object ExportJwk(bool canonical = false)
{
// Note, we only produce a canonical form of the JWK
@@ -95,13 +109,9 @@ public object ExportJwk(bool canonical = false)
if (_jwk == null)
{
var keyParams = _rsa.ExportParameters(false);
- _jwk = new
+ _jwk = new RSJwk
{
- // As per RFC 7638 Section 3, these are the *required* elements of the
- // JWK and are sorted in lexicographic order to produce a canonical form
-
e = CryptoHelper.Base64.UrlEncode(keyParams.Exponent),
- kty = "RSA", // https://tools.ietf.org/html/rfc7518#section-6.3
n = CryptoHelper.Base64.UrlEncode(keyParams.Modulus),
};
}
diff --git a/src/ACMESharp/HTTP/Link.cs b/src/ACMESharp/HTTP/Link.cs
index 4426d1a..184ab3c 100644
--- a/src/ACMESharp/HTTP/Link.cs
+++ b/src/ACMESharp/HTTP/Link.cs
@@ -1,4 +1,4 @@
-using System;
+using System;
using System.Text.RegularExpressions;
namespace ACMESharp.HTTP
@@ -19,7 +19,7 @@ public class Link
///
/// Regex pattern to match and extract the components of an HTTP related link header.
///
- public static readonly Regex LinkHeaderRegex = new Regex("<(.+)>;rel=\"(.+)\"");
+ public static readonly Regex LinkHeaderRegex = new Regex("<(.+)>;[ ]?rel=\"(.+)\"");
public const string LinkHeaderFormat = "<{0}>;rel={1}";
diff --git a/src/ACMESharp/Protocol/AcmeProtocolClient.cs b/src/ACMESharp/Protocol/AcmeProtocolClient.cs
index a6aae80..c1bc950 100644
--- a/src/ACMESharp/Protocol/AcmeProtocolClient.cs
+++ b/src/ACMESharp/Protocol/AcmeProtocolClient.cs
@@ -191,7 +191,7 @@ public async Task CreateAccountAsync(IEnumerable contact
{
Contact = contacts,
TermsOfServiceAgreed = termsOfServiceAgreed,
- ExternalAccountBinding = (JwsSignedPayload)externalAccountBinding,
+ ExternalAccountBinding = externalAccountBinding,
};
var resp = await SendAcmeAsync(
new Uri(_http.BaseAddress, Directory.NewAccount),
@@ -943,4 +943,4 @@ public void Dispose()
}
#endregion
}
-}
\ No newline at end of file
+}
diff --git a/test/ACMESharp.UnitTests/JwsTests.cs b/test/ACMESharp.UnitTests/JwsTests.cs
index d1e3454..f35a416 100644
--- a/test/ACMESharp.UnitTests/JwsTests.cs
+++ b/test/ACMESharp.UnitTests/JwsTests.cs
@@ -1,7 +1,9 @@
using System;
+using System.Security.Cryptography;
using System.Text;
using System.Text.RegularExpressions;
using ACMESharp.Crypto;
+using ACMESharp.Crypto.JOSE.Impl;
using Microsoft.VisualStudio.TestTools.UnitTesting;
namespace ACMESharp.UnitTests
@@ -305,5 +307,48 @@ public void TestRfc7515Example_A_2_1()
string sigB64uActual = CryptoHelper.Base64.UrlEncode(sigActual);
Assert.AreEqual(sigB64uExpected, sigB64uActual);
}
+
+ [TestMethod]
+ public void SerDesEC()
+ {
+ var rng = RandomNumberGenerator.Create();
+ for (var i = 0; i < 1000; i++) {
+ var original = new ESJwsTool(); // Default for ISigner
+ original.Init();
+ var rawX = new byte[8034];
+ rng.GetBytes(rawX);
+ var sigX = original.Sign(rawX);
+
+ var exported = original.Export();
+ var copy = new ESJwsTool();
+ copy.Init();
+ copy.Import(exported);
+ var verified = copy.Verify(rawX, sigX);
+
+ Assert.AreEqual(true, verified);
+ }
+ }
+
+ [TestMethod]
+ public void SerDesRSA()
+ {
+ var rng = RandomNumberGenerator.Create();
+ for (var i = 0; i < 1000; i++)
+ {
+ var original = new RSJwsTool(); // Default for ISigner
+ original.Init();
+ var rawX = new byte[8034];
+ rng.GetBytes(rawX);
+ var sigX = original.Sign(rawX);
+
+ var exported = original.Export();
+ var copy = new RSJwsTool();
+ copy.Init();
+ copy.Import(exported);
+ var verified = copy.Verify(rawX, sigX);
+
+ Assert.AreEqual(true, verified);
+ }
+ }
}
}
\ No newline at end of file