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