private void ValidateRequestHeader(AcmeHeader header, string requestUrl) { if (header is null) { throw new ArgumentNullException(nameof(header)); } _logger.LogDebug("Attempting to validate AcmeHeader ..."); if (!Uri.IsWellFormedUriString(header.Url, UriKind.RelativeOrAbsolute)) { throw new MalformedRequestException("Header Url is not well-formed."); } if (header.Url != requestUrl) { throw new NotAuthorizedException(); } if (!_supportedAlgs.Contains(header.Alg)) { throw new BadSignatureAlgorithmException(); } if (header.Jwk != null && header.Kid != null) { throw new MalformedRequestException("Do not provide both Jwk and Kid."); } if (header.Jwk == null && header.Kid == null) { throw new MalformedRequestException("Provide either Jwk or Kid."); } _logger.LogDebug("successfully validated AcmeHeader."); }
private async Task <TResult> SendAsync <TResult>(HttpMethod method, Uri uri, object message) where TResult : class { Verbose($"{method} {uri} {message?.GetType()}"); var nonceHeader = new AcmeHeader { Nonce = nonce }; Verbose($"sending nonce {nonce}"); HttpContent content = null; if (message != null) { var encodedMessage = jws.Encode(message, nonceHeader); var json = JsonConvert.SerializeObject(encodedMessage, new JsonSerializerSettings() { NullValueHandling = NullValueHandling.Ignore, Formatting = Formatting.Indented }); content = new StringContent(json, Encoding.UTF8, "application/json"); } var request = new HttpRequestMessage(method, uri) { Content = content }; var response = await client.SendAsync(request).ConfigureAwait(false); Verbose($"response status: {(int)response.StatusCode} {response.ReasonPhrase}"); RememberNonce(response); if (response.Content.Headers.ContentType.MediaType == "application/problem+json") { var problemJson = await response.Content.ReadAsStringAsync().ConfigureAwait(false); var problem = JsonConvert.DeserializeObject <Problem>(problemJson); Verbose($"error response from server: {problem.Type}: {problem.Detail}"); throw new AcmeException(problem, response); } if (typeof(TResult) == typeof(CertificateResponse) && response.Content.Headers.ContentType.MediaType == "application/pkix-cert") { var certificateBytes = await response.Content.ReadAsByteArrayAsync().ConfigureAwait(false); var certificateResponse = new CertificateResponse() { Certificate = certificateBytes }; GetHeaderValues(response, certificateResponse); return(certificateResponse as TResult); } var responseContent = await response.Content.ReadAsAsync <TResult>().ConfigureAwait(false); GetHeaderValues(response, responseContent); return(responseContent); }
private async Task <TResult> SendAsync <TResult>(HttpMethod method, Uri uri, object message, CancellationToken token) where TResult : class { var nonceHeader = new AcmeHeader { Nonce = _nonce }; var request = new HttpRequestMessage(method, uri); if (message != null) { var encodedMessage = _jws.Encode(message, nonceHeader); var json = JsonConvert.SerializeObject(encodedMessage, jsonSettings); request.Content = new StringContent(json, Encoding.UTF8, "application/json"); } var response = await _client.SendAsync(request, token).ConfigureAwait(false); RememberNonce(response); if (response.Content.Headers.ContentType.MediaType == "application/problem+json") { var problemJson = await response.Content.ReadAsStringAsync().ConfigureAwait(false); var problem = JsonConvert.DeserializeObject <Problem>(problemJson); throw new AcmeException(problem, response); } if (typeof(TResult) == typeof(CertificateResponse) && response.Content.Headers.ContentType.MediaType == "application/pkix-cert") { var certificateBytes = await response.Content.ReadAsByteArrayAsync().ConfigureAwait(false); var certificateResponse = new CertificateResponse { Certificate = certificateBytes }; GetHeaderValues(response, certificateResponse); return(certificateResponse as TResult); } var responseText = await response.Content.ReadAsStringAsync().ConfigureAwait(false); var responseContent = JObject.Parse(responseText).ToObject <TResult>(); GetHeaderValues(response, responseContent); return(responseContent); }
private async Task ValidateSignatureAsync(AcmeRawPostRequest request, AcmeHeader header, CancellationToken cancellationToken) { if (request is null) { throw new ArgumentNullException(nameof(request)); } if (header is null) { throw new ArgumentNullException(nameof(header)); } _logger.LogDebug("Attempting to validate signature ..."); var jwk = header.Jwk; if (jwk == null) { try { var accountId = header.GetAccountId(); var account = await _accountService.LoadAcountAsync(accountId, cancellationToken); jwk = account?.Jwk; } catch (InvalidOperationException) { throw new MalformedRequestException("KID could not be found."); } } if (jwk == null) { throw new MalformedRequestException("Could not load JWK."); } var securityKey = jwk.SecurityKey; using var signatureProvider = new AsymmetricSignatureProvider(securityKey, header.Alg); var plainText = System.Text.Encoding.UTF8.GetBytes($"{request.Header}.{request.Payload ?? ""}"); var signature = Base64UrlEncoder.DecodeBytes(request.Signature); if (!signatureProvider.Verify(plainText, signature)) { throw new MalformedRequestException("The signature could not be verified"); } _logger.LogDebug("successfully validated signature."); }
public async Task ValidateRequestAsync(AcmeRawPostRequest request, AcmeHeader header, string requestUrl, CancellationToken cancellationToken) { if (request is null) { throw new ArgumentNullException(nameof(request)); } if (header is null) { throw new ArgumentNullException(nameof(header)); } if (string.IsNullOrWhiteSpace(requestUrl)) { throw new ArgumentNullException(nameof(requestUrl)); } ValidateRequestHeader(header, requestUrl); await ValidateNonceAsync(header.Nonce, cancellationToken); await ValidateSignatureAsync(request, header, cancellationToken); }
private async Task <TResult> SendAsync <TResult>(HttpMethod method, Uri uri, object message, CancellationToken token) where TResult : class { var hasNonce = _nonce != null; HttpResponseMessage response; do { var nonceHeader = new AcmeHeader { Nonce = _nonce }; var request = new HttpRequestMessage(method, uri); if (message != null) { var encodedMessage = _jws.Encode(message, nonceHeader); var json = JsonConvert.SerializeObject(encodedMessage, jsonSettings); request.Content = new StringContent(json, Encoding.UTF8, "application/json"); } response = await _client.SendAsync(request, token).ConfigureAwait(false); if (response.Headers.TryGetValues("Replay-Nonce", out var vals)) { _nonce = vals.FirstOrDefault(); } else { _nonce = null; } if (response.IsSuccessStatusCode || hasNonce || _nonce == null) { break; // either successful or no point in retry } hasNonce = true; // we only allow it once } while (true); if (response.Content.Headers.ContentType.MediaType == "application/problem+json") { var problemJson = await response.Content.ReadAsStringAsync().ConfigureAwait(false); var problem = JsonConvert.DeserializeObject <Problem>(problemJson); throw new AcmeException(problem, response); } if (typeof(TResult) == typeof(CertificateResponse) && response.Content.Headers.ContentType.MediaType == "application/pkix-cert") { var certificateBytes = await response.Content.ReadAsByteArrayAsync().ConfigureAwait(false); var certificateResponse = new CertificateResponse { Certificate = certificateBytes }; GetHeaderValues(response, certificateResponse); return(certificateResponse as TResult); } var responseText = await response.Content.ReadAsStringAsync().ConfigureAwait(false); var responseContent = JObject.Parse(responseText).ToObject <TResult>(); GetHeaderValues(response, responseContent); return(responseContent); }
private async Task <ActionResult <Protocol.HttpModel.Account> > CreateAccountAsync(AcmeHeader header, AcmePayload <CreateOrGetAccount> payload) { if (payload == null) { throw new MalformedRequestException("Payload was empty or could not be read."); } var account = await _accountService.CreateAccountAsync( header.Jwk !, //Post requests are validated, JWK exists. payload.Value.Contact, payload.Value.TermsOfServiceAgreed, HttpContext.RequestAborted); var ordersUrl = Url.RouteUrl("OrderList", new { accountId = account.AccountId }, "https"); var accountResponse = new Protocol.HttpModel.Account(account, ordersUrl); var accountUrl = Url.RouteUrl("Account", new { accountId = account.AccountId }, "https"); return(new CreatedResult(accountUrl, accountResponse)); }
public async Task <ActionResult <Protocol.HttpModel.Account> > CreateOrGetAccount(AcmeHeader header, AcmePayload <CreateOrGetAccount> payload) { if (payload.Value.OnlyReturnExisting) { return(await FindAccountAsync(payload)); } return(await CreateAccountAsync(header, payload)); }