/// <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); }
/// <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)); }
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); }
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); }
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); }
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); }
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); }