Example #1
0
        /// <summary>
        /// Rotates the current Public key that is associated with this Account by the target ACME CA with a new Public key.
        /// </summary>
        /// <see cref="https://tools.ietf.org/html/draft-ietf-acme-acme-18#section-7.3.5"/>
        public async Task <AcmeResponse <Protocol.Account> > AccountChangeKeyAsync(AsymmetricAlgorithm keyNew)
        {
            Logger.Info("Changing an ACME account key. Params:{@params}", keyNew);

            var jws    = new JsonWebSignature();
            var jwk    = new JsonWebKey(Key);
            var jwkNew = new JsonWebKey(keyNew);

            jws.SetPayload(new Protocol.Messages.ChangeKey
            {
                Account = Location,
                Key     = jwk,
            });
            jws.SetProtected(new JsonWebSignatureProtected
            {
                Algorithm = AlgorithmsEnum.RS256,
                Url       = Directory.KeyChange,
                Key       = jwkNew,
            });
            jws.Sign(keyNew);

            var response = await Request(GetType(typeof(Protocol.Account)), Directory.KeyChange,
                                         new RequestParams
            {
                Method  = HttpMethod.Post,
                Payload = jws
            });

            Key = keyNew;

            return(response);
        }
Example #2
0
        /// <summary>
        /// Account create with external account binding.
        /// </summary>
        /// <see cref="https://tools.ietf.org/html/draft-ietf-acme-acme-12#section-7.3.5"/>
        public async Task <AcmeResponse <Protocol.Account> > AccountCreateAsync(Protocol.Messages.NewAccount account, string kid, string keyMac)
        {
            Logger.Info("Creating a new ACME account with external account binding. Params:{@params}", new
            {
                Account = account,
                KeyId   = kid,
                KeyMac  = keyMac,
            });

            var jws = new JsonWebSignature();

            jws.SetProtected(new JsonWebSignatureProtected
            {
                Algorithm = AlgorithmsEnum.HS256,
                KeyID     = kid,
                Url       = Directory.NewAccount,
            });

            jws.SetPayload(new JsonWebKey(Key));

            var key = HMAC.Create("HMACSHA256");

            key.Key = Base64Url.Decode(keyMac);

            jws.Sign(key);

            account.ExternalAccountBinding = jws;

            return(await AccountCreateAsync(account));
        }
Example #3
0
        public void Account_New_TermsOfService_Enabled_TermsOfServiceAgreed_False()
        {
            AcmeResponse response = null;

            using var provider = Application.CreateProvider(o =>
            {
                o.TermsOfService = "https://some.com/acme/terms.pdf";
            });
            IAcmeController controller = (IAcmeController)provider.GetService(typeof(IAcmeController));

            // Get nonce
            response = controller.GetNonce(new AcmeRequest
            {
                Path   = $"{Application.BaseAddress}new-nonce",
                Method = "HEAD",
            });

            // Create token
            var accountKey = RSA.Create(2048);
            var token      = new JsonWebSignature();

            token.SetProtected(new JsonWebSignatureProtected
            {
                Url       = $"{Application.BaseAddress}new-acct",
                Algorithm = AlgorithmsEnum.RS256,
                Key       = new JsonWebKey(accountKey),
                Nonce     = response.Headers.ReplayNonce,
            });
            token.SetPayload(new NewAccount
            {
                Contacts             = new string[] { "mailto:[email protected]" },
                TermsOfServiceAgreed = false,
            });
            token.Sign(accountKey);

            // Create account
            response = controller.CreateAccount(new AcmeRequest
            {
                Path   = $"{Application.BaseAddress}new-acct",
                Method = "POST",
                Token  = token,
            });

            Assert.Equal(400, response.StatusCode);

            var content = response.GetContent <Protocol.Error>();

            Assert.Equal(Protocol.ErrorType.Malformed, content.Type);
            Assert.NotNull(content.Detail);
        }
Example #4
0
        public void Account_New_TermsOfService_Disabled()
        {
            AcmeResponse response = null;

            using var provider = Application.CreateProvider();
            IAcmeController controller = (IAcmeController)provider.GetService(typeof(IAcmeController));

            // Get nonce
            response = controller.GetNonce(new AcmeRequest
            {
                Path   = $"{Application.BaseAddress}new-nonce",
                Method = "HEAD",
            });

            // Create token
            var accountKey = RSA.Create(2048);
            var token      = new JsonWebSignature();

            token.SetProtected(new JsonWebSignatureProtected
            {
                Url       = $"{Application.BaseAddress}new-acct",
                Algorithm = AlgorithmsEnum.RS256,
                Key       = new JsonWebKey(accountKey),
                Nonce     = response.Headers.ReplayNonce,
            });
            token.SetPayload(new NewAccount
            {
                Contacts = new string[] { "mailto:[email protected]" },
            });
            token.Sign(accountKey);

            // Create account
            response = controller.CreateAccount(new AcmeRequest
            {
                Path   = $"{Application.BaseAddress}new-acct",
                Method = "POST",
                Token  = token,
            });

            Assert.Equal(201, response.StatusCode);

            var content = response.GetContent <Protocol.Account>();

            Assert.Single(response.Headers.Link);
            Assert.Equal("https://test.server.com/acme/acct/1", response.Headers.Location);
            Assert.NotNull(content);
            Assert.NotNull(content.Key);
            Assert.Null(content.TermsOfServiceAgreed);
        }
Example #5
0
        public void Account_ChangeKey_DuplicateKey()
        {
            AcmeResponse response = null;

            using var provider = Application.CreateProvider();
            IAcmeController controller = (IAcmeController)provider.GetService(typeof(IAcmeController));

            // Get nonce
            response = controller.GetNonce(new AcmeRequest
            {
                Path   = $"{Application.BaseAddress}new-nonce",
                Method = "HEAD",
            });

            // Create token
            var accountKey = RSA.Create(2048);
            var token      = new JsonWebSignature();

            token.SetProtected(new JsonWebSignatureProtected
            {
                Url       = $"{Application.BaseAddress}new-acct",
                Algorithm = AlgorithmsEnum.RS256,
                Key       = new JsonWebKey(accountKey),
                Nonce     = response.Headers.ReplayNonce,
            });
            token.SetPayload(new NewAccount
            {
                Contacts = new string[] { "mailto:[email protected]" },
            });
            token.Sign(accountKey);

            // Create account
            response = controller.CreateAccount(new AcmeRequest
            {
                Path   = $"{Application.BaseAddress}new-acct",
                Method = "POST",
                Token  = token,
            });

            Assert.Equal(201, response.StatusCode);

            // Create token
            var duplicateKey = RSA.Create(2048);

            token = new JsonWebSignature();
            token.SetProtected(new JsonWebSignatureProtected
            {
                Url       = $"{Application.BaseAddress}new-acct",
                Algorithm = AlgorithmsEnum.RS256,
                Key       = new JsonWebKey(duplicateKey),
                Nonce     = response.Headers.ReplayNonce,
            });
            token.SetPayload(new NewAccount
            {
                Contacts = new string[] { "mailto:[email protected]" },
            });
            token.Sign(duplicateKey);

            // Create account
            response = controller.CreateAccount(new AcmeRequest
            {
                Path   = $"{Application.BaseAddress}new-acct",
                Method = "POST",
                Token  = token,
            });

            Assert.Equal(201, response.StatusCode);

            token = new JsonWebSignature();
            token.SetProtected(new JsonWebSignatureProtected
            {
                Url       = $"{Application.BaseAddress}key-change",
                Algorithm = AlgorithmsEnum.RS256,
                KeyID     = $"{Application.BaseAddress}acct/1",
                Nonce     = response.Headers.ReplayNonce,
            });
            #region New key
            var newKeyToken = new JsonWebSignature();
            newKeyToken.SetProtected(new JsonWebSignatureProtected
            {
                Url       = $"{Application.BaseAddress}key-change",
                Algorithm = AlgorithmsEnum.RS256,
                Key       = new JsonWebKey(duplicateKey),
            });
            newKeyToken.SetPayload(new ChangeKey
            {
                Account = $"{Application.BaseAddress}acct/1",
                Key     = new JsonWebKey(accountKey),
            });
            newKeyToken.Sign(duplicateKey);
            #endregion

            token.SetPayload(newKeyToken);
            token.Sign(accountKey);

            // Update account
            response = controller.ChangeKey(new AcmeRequest
            {
                Path   = $"{Application.BaseAddress}key-change",
                Method = "POST",
                Token  = token,
            });

            Assert.Equal(409, response.StatusCode);
            Assert.Equal($"{Application.BaseAddress}acct/2", response.Headers.Location);

            var content = response.GetContent <Protocol.Error>();
            Assert.Equal(Protocol.ErrorType.Malformed, content.Type);
            Assert.NotNull(content.Detail);
        }
Example #6
0
        public void Account_Deactivate()
        {
            AcmeResponse response = null;

            using var provider = Application.CreateProvider();
            IAcmeController controller = (IAcmeController)provider.GetService(typeof(IAcmeController));

            // Get nonce
            response = controller.GetNonce(new AcmeRequest
            {
                Path   = $"{Application.BaseAddress}new-nonce",
                Method = "HEAD",
            });

            // Create token
            var accountKey = RSA.Create(2048);
            var token      = new JsonWebSignature();

            token.SetProtected(new JsonWebSignatureProtected
            {
                Url       = $"{Application.BaseAddress}new-acct",
                Algorithm = AlgorithmsEnum.RS256,
                Key       = new JsonWebKey(accountKey),
                Nonce     = response.Headers.ReplayNonce,
            });
            token.SetPayload(new NewAccount
            {
                Contacts = new string[] { "mailto:[email protected]" },
            });
            token.Sign(accountKey);

            // Create account
            response = controller.CreateAccount(new AcmeRequest
            {
                Path   = $"{Application.BaseAddress}new-acct",
                Method = "POST",
                Token  = token,
            });

            Assert.Equal(201, response.StatusCode);

            token = new JsonWebSignature();
            token.SetProtected(new JsonWebSignatureProtected
            {
                Url       = response.Headers.Location,
                Algorithm = AlgorithmsEnum.RS256,
                KeyID     = response.Headers.Location,
                Nonce     = response.Headers.ReplayNonce,
            });
            token.SetPayload(new UpdateAccount
            {
                Contacts = new string[] { "mailto:[email protected]" },
                Status   = Protocol.AccountStatus.Deactivated,
            });
            token.Sign(accountKey);

            // Update account
            response = controller.PostAccount(new AcmeRequest
            {
                Path   = response.Headers.Location,
                Method = "POST",
                Token  = token,
            });

            Assert.Equal(200, response.StatusCode);

            var content = response.GetContent <Protocol.Account>();

            Assert.Equal("mailto:[email protected]", content.Contacts[0]);
            Assert.Equal(Protocol.AccountStatus.Deactivated, content.Status);
        }
Example #7
0
        protected async Task <AcmeResponse> Request(
            string url,
            RequestParams @params = null
            )
        {
            @params = @params ?? new RequestParams();

            string content = null;

            var request = new HttpRequestMessage(@params.Method, url);

            if (@params.Payload != null)
            {
                var jws = new JsonWebSignature
                {
                    Payload = "",
                };

                jws.SetProtected(new JsonWebSignatureProtected
                {
                    Algorithm = AlgorithmsEnum.RS256,
                    Nonce     = Nonce,
                    Url       = url,
                    KeyID     = @params.IncludePublicKey ? null : Location,
                    Key       = @params.IncludePublicKey ? new JsonWebKey(Key) : null
                });

                if (!(@params.Payload is string))
                {
                    jws.SetPayload(@params.Payload);
                }

                jws.Sign(Key);

                content = JsonConvert.SerializeObject(jws, Formatting.Indented);
            }

            if (!string.IsNullOrEmpty(content))
            {
                request.Content = new StringContent(content);
                request.Content.Headers.ContentType = MediaTypeHeader.JsonContentTypeHeaderValue;
            }

            // Copy headers to HTTP request from ACME request
            foreach (var key in @params.Headers.AllKeys)
            {
                request.Headers.Add(key, @params.Headers.Get(key));
            }

            Logger.Debug("Request {method} {path} {token}", @params.Method, url, @params.Headers.ToString(), content);

            var response = _http.SendAsync(request).Result;

            // Get Replay-Nonce from response
            IEnumerable <string> nonceList;

            response.Headers.TryGetValues(HeaderCollection.ReplayNonceHeader, out nonceList);
            Nonce = nonceList?.FirstOrDefault();

            try
            {
                response.EnsureSuccessStatusCode();
            }
            catch (HttpRequestException)
            {
                string         message   = null;
                string         errorJson = null;
                Protocol.Error error     = null;
                try
                {
                    errorJson = await response.Content.ReadAsStringAsync();

                    try
                    {
                        error = JsonConvert.DeserializeObject <Protocol.Error>(errorJson);

                        message = $"{error.Type}: {error.Detail}";
                        Logger.Debug("ACME Error {@error}", error);
                    }
                    catch
                    {
                        Logger.Error("Cannot parse ACME Error from Client response");
                        Logger.Debug(errorJson);
                    }
                }
                catch (System.Exception e)
                {
                    Logger.Error("Cannot parse content from Client response");
                    Logger.Debug(e);
                }

                AcmeException ex;
                if (string.IsNullOrEmpty(message))
                {
                    message = $"Unexpected response status code [{response.StatusCode}] for [{@params.Method}] request to [{url}]";
                    ex      = new AcmeException(Protocol.ErrorType.ServerInternal, message, response.StatusCode);
                }
                else
                {
                    ex = new AcmeException(error.Type, error.Detail, response.StatusCode);
                }

                Logger.Error(ex);

                throw ex;
            }

            // Copy headers to ACME Response
            var headers = new HeaderCollection();

            foreach (var header in response.Headers)
            {
                headers.Add(header.Key, string.Join(", ", header.Value));
            }

            var acmeResponse = new AcmeResponse
            {
                StatusCode = (int)response.StatusCode,
                Headers    = headers,
                Content    = response.Content == null
                    ? new MediaTypeContent("", new byte[0])
                    : new MediaTypeContent(
                    response.Content.Headers.ContentType?.MediaType ?? "",
                    await response.Content.ReadAsByteArrayAsync()),
            };

            Logger.Debug("Response {@response}", acmeResponse);

            return(acmeResponse);
        }