protected async Task ProcessOrderAuthorizations(IOrderContext orderCtx, IAcmeContext acmeCtx, CertificateInfo certInfo) { // Пройти по всем авторизациям и создать необходимые DNS записи (DNS challenge) // или текстовые файлы (HTTP challenge) var authorizations = (await orderCtx.Authorizations()).ToList(); foreach (IAuthorizationContext authCtx in authorizations) { Challenge challenge = null; foreach (var handler in challengeHandlers.OrderBy(x => x.Priority)) { challenge = await handler.DoChallengeAsync(authCtx, acmeCtx, orderCtx, certInfo); if (challenge != null) { break; } } //Если не нашлось ни одного handler'а для обработки авторизации, то выкинуть исключение это означает, //что мы не можем завершить подтвержденрие владения доменом if (challenge == null) { throw new InvalidOperationException($"Can't complete authorizations for certificate {certInfo.Name}"); } } }
/// <summary> /// Posts the data to the specified URI. /// </summary> /// <typeparam name="T">The type of expected result</typeparam> /// <param name="client">The client.</param> /// <param name="context">The context.</param> /// <param name="location">The URI.</param> /// <param name="entity">The payload.</param> /// <param name="ensureSuccessStatusCode">if set to <c>true</c>, throw exception if the request failed.</param> /// <returns> /// The response from ACME server. /// </returns> /// <exception cref="Exception"> /// If the HTTP request failed and <paramref name="ensureSuccessStatusCode"/> is <c>true</c>. /// </exception> internal static async Task <AcmeHttpResponse <T> > Post <T>(this IAcmeHttpClient client, IAcmeContext context, Uri location, object entity, bool ensureSuccessStatusCode) { var payload = await context.Sign(entity, location); var response = await client.Post <T>(location, payload); var retryCount = context.BadNonceRetryCount; while (response.Error?.Status == System.Net.HttpStatusCode.BadRequest && response.Error.Type?.CompareTo("urn:ietf:params:acme:error:badNonce") == 0 && retryCount-- > 0) { payload = await context.Sign(entity, location); response = await client.Post <T>(location, payload); } if (ensureSuccessStatusCode && response.Error != null) { throw new AcmeRequestException( string.Format(Strings.ErrorFetchResource, location), response.Error); } return(response); }
private async Task UseExistingLetsEncryptAccount(IKey existingAccountKey) { _logger.LogDebug("Using existing LetsEncrypt account."); acme = new AcmeContext(LetsEncryptUri, existingAccountKey); await acme.Account(); }
/// <summary> /// Initializes a new instance of the <see cref="EntityContext{T}"/> class. /// </summary> /// <param name="context">The context.</param> /// <param name="location">The location.</param> public EntityContext( IAcmeContext context, Uri location) { Context = context; Location = location; }
/// <summary> /// Post to the new account endpoint. /// </summary> /// <param name="context">The ACME context.</param> /// <param name="body">The payload.</param> /// <param name="ensureSuccessStatusCode">if set to <c>true</c>, throw exception if the request failed.</param> /// <param name="eabKeyId">Optional key identifier, if using external account binding.</param> /// <param name="eabKey">Optional EAB key, if using external account binding.</param> /// <param name="eabKeyAlg">Optional EAB key algorithm, if using external account binding, defaults to HS256 if not specified</param> /// <returns>The ACME response.</returns> internal static async Task <AcmeHttpResponse <Account> > NewAccount( IAcmeContext context, Account body, bool ensureSuccessStatusCode, string eabKeyId = null, string eabKey = null, string eabKeyAlg = null) { var endpoint = await context.GetResourceUri(d => d.NewAccount); var jws = new JwsSigner(context.AccountKey); if (eabKeyId != null && eabKey != null) { var header = new { alg = eabKeyAlg?.ToUpper() ?? "HS256", kid = eabKeyId, url = endpoint }; var headerJson = Newtonsoft.Json.JsonConvert.SerializeObject(header, Newtonsoft.Json.Formatting.None, JsonUtil.CreateSettings()); var protectedHeaderBase64 = JwsConvert.ToBase64String(System.Text.Encoding.UTF8.GetBytes(headerJson)); var accountKeyBase64 = JwsConvert.ToBase64String( System.Text.Encoding.UTF8.GetBytes( Newtonsoft.Json.JsonConvert.SerializeObject(context.AccountKey.JsonWebKey, Newtonsoft.Json.Formatting.None) ) ); var signingBytes = System.Text.Encoding.ASCII.GetBytes($"{protectedHeaderBase64}.{accountKeyBase64}"); // eab signature is the hash of the header and account key, using the eab key byte[] signatureHash; switch (header.alg) { case "HS512": using (var hs512 = new HMACSHA512(JwsConvert.FromBase64String(eabKey))) signatureHash = hs512.ComputeHash(signingBytes); break; case "HS384": using (var hs384 = new HMACSHA384(JwsConvert.FromBase64String(eabKey))) signatureHash = hs384.ComputeHash(signingBytes); break; default: using (var hs256 = new HMACSHA256(JwsConvert.FromBase64String(eabKey))) signatureHash = hs256.ComputeHash(signingBytes); break; } var signatureBase64 = JwsConvert.ToBase64String(signatureHash); body.ExternalAccountBinding = new { Protected = protectedHeaderBase64, Payload = accountKeyBase64, Signature = signatureBase64 }; } return(await context.HttpClient.Post <Account>(jws, endpoint, body, ensureSuccessStatusCode)); }
private async Task CreateNewLetsEncryptAccount() { _logger.LogDebug("Creating LetsEncrypt account with email {0}.", _options.Email); acme = new AcmeContext(LetsEncryptUri); await acme.NewAccount(_options.Email, true); await _persistenceService.PersistAccountCertificateAsync(acme.AccountKey); }
/// <summary> /// Creates a factory for testing that will always return the specific context. /// </summary> /// <param name="acmeContext"></param> /// <returns></returns> public static Mock <IAcmeContextFactory> CreateFactoryMock(this IAcmeContext acmeContext) { var contextFactory = new Mock <IAcmeContextFactory>(); contextFactory.Setup(x => x.GetContext(It.IsAny <Uri>(), It.IsAny <IKey>())) .Returns(acmeContext); return(contextFactory); }
/// <summary> /// All new /// </summary> /// <param name="acmeContext"></param> /// <param name="accountContext"></param> /// <param name="orderContext"></param> /// <param name="workDir"></param> /// <param name="loggerFactory"></param> public FreeCertContext(IAcmeContext acmeContext, IAccountContext accountContext, IOrderContext orderContext, string workDir, ILoggerFactory loggerFactory) { _workDir = workDir; _loggerFactory = loggerFactory; AcmeContext = acmeContext; AccountContext = accountContext; OrderContext = orderContext; _dnsResolver = new DnsResolver(_loggerFactory.CreateLogger <DnsResolver>()); }
/// <summary> /// Post to the new account endpoint. /// </summary> /// <param name="context">The ACME context.</param> /// <param name="body">The payload.</param> /// <param name="ensureSuccessStatusCode">if set to <c>true</c>, throw exception if the request failed.</param> /// <returns>The ACME response.</returns> internal static async Task <AcmeHttpResponse <Account> > NewAccount( IAcmeContext context, Account body, bool ensureSuccessStatusCode) { var endpoint = await context.GetResourceUri(d => d.NewAccount); var jws = new JwsSigner(context.AccountKey); var payload = jws.Sign(body, url: endpoint, nonce: await context.HttpClient.ConsumeNonce()); return(await context.HttpClient.Post <Account>(endpoint, payload, ensureSuccessStatusCode)); }
/// <summary> /// Initializes a new instance of the <see cref="ChallengeContext"/> class. /// </summary> /// <param name="context">The context.</param> /// <param name="location">The location.</param> /// <param name="type">The type.</param> /// <param name="token">The token.</param> public ChallengeContext( IAcmeContext context, Uri location, string type, string token) : base(context, location) { Type = type; Token = token; }
/// <summary> /// Gets a resource URI. /// </summary> /// <param name="context">The ACME context.</param> /// <param name="getter">The getter to retrieve resource URI from <see cref="Directory"/>.</param> /// <param name="optional">if set to <c>true</c>, the resource is optional.</param> /// <returns>The resource URI, or <c>null</c> if not found</returns> /// <exception cref="NotSupportedException">If the ACME operation not supported.</exception> internal static async Task <Uri> GetResourceUri(this IAcmeContext context, Func <Directory, Uri> getter, bool optional = false) { var dir = await context.GetDirectory(); var uri = getter(dir); if (!optional && uri == null) { throw new NotSupportedException("ACME operation not supported."); } return(uri); }
public async Task <IKey> AuthenticateWithNewAccount( string email, Uri acmeServer, bool termsOfServiceAgreed, CancellationToken cancellationToken) { _logger.LogDebug("Acme authenticating and creating Let's Encrypt account with email {email}...", email); cancellationToken.ThrowIfCancellationRequested(); if (_acmeContext != null) { return(_acmeContext.AccountKey); } _acmeContext = new AcmeContext(acmeServer); await _acmeContext.NewAccount(email, true); return(_acmeContext.AccountKey); }
public async Task <IKey> AuthenticateWithExistingAccount( Uri acmeServer, IKey account, CancellationToken cancellationToken) { _logger.LogDebug("Acme authenticating using existing Let's Encrypt account..."); cancellationToken.ThrowIfCancellationRequested(); if (_acmeContext != null) { return(_acmeContext.AccountKey); } _acmeContext = new AcmeContext(acmeServer, account); await _acmeContext.Account(); return(_acmeContext.AccountKey); }
/// <summary> /// Creates an account. /// </summary> /// <param name="context">The ACME context.</param> /// <param name="email">The email.</param> /// <param name="termsOfServiceAgreed">Set to <c>true</c> to accept the terms of service.</param> /// <param name="eabKeyId">Optional key identifier for external account binding</param> /// <param name="eabKey">Optional key for use with external account binding</param> /// <param name="eabKeyAlg">Optional key algorithm e.g HS256, for external account binding</param> /// <returns> /// The account created. /// </returns> public static Task <IAccountContext> NewAccount(this IAcmeContext context, string email, bool termsOfServiceAgreed = false, string eabKeyId = null, string eabKey = null, string eabKeyAlg = null) => context.NewAccount(new[] { $"mailto:{email}" }, termsOfServiceAgreed, eabKeyId, eabKey, eabKeyAlg);
/// <summary> /// Initializes a new instance of the <see cref="AccountContext" /> class. /// </summary> /// <param name="context">The context.</param> /// <param name="location">The location.</param> public AccountContext(IAcmeContext context, Uri location) : base(context, location) { }
/// <summary> /// Initializes a new instance of the <see cref="AuthorizationContext"/> class. /// </summary> /// <param name="context">The context.</param> /// <param name="location">The location.</param> public AuthorizationContext( IAcmeContext context, Uri location) : base(context, location) { }
public async Task <Challenge> DoChallengeAsync(IAuthorizationContext authCtx, IAcmeContext acmeCtx, IOrderContext orderCtx, CertificateInfo certificateInfo) { try { var dnsInfo = certificateInfo.DnsChallenge; if (dnsInfo == null) { return(null); } var domain = (await authCtx.Resource()).Identifier.Value; var challenge = await authCtx.Dns(); if (challenge == null) { return(null); } logger.LogInformation("Process DNS Challenge for domain: {domain}", domain); var connector = dnsConnectors.FirstOrDefault(x => x.Name == dnsInfo.Provider); if (connector == null) { throw new InvalidOperationException($"Can't find DNS provider {dnsInfo.Provider}"); } await connector.CreateDnsChallengeRecordAsync(domain, acmeCtx.AccountKey.DnsTxt(challenge.Token), dnsInfo, orderCtx.Location.ToString()); return(await challenge.Validate()); } catch (Exception e) { logger.LogError(e, "Error in DNS authorization processing!"); return(null); } }
public OrderContext( IAcmeContext context, Uri location) : base(context, location) { }
public LetsEncryptClient(IAcmeContext acme, LetsEncryptOptions options, ILogger logger) { _logger = logger; _acme = acme; _options = options; }
public AuthenticationContext(IAcmeContext acme, IAcmeOptions options) { AcmeContext = acme ?? throw new ArgumentNullException(nameof(acme)); Options = options ?? throw new ArgumentNullException(nameof(options)); }
/// <summary> /// Gets the terms of service link from the ACME server. /// </summary> /// <param name="context">The ACME context.</param> /// <returns>The terms of service link.</returns> public static async Task <Uri> TermsOfService(this IAcmeContext context) { var dir = await context.GetDirectory(); return(dir.Meta?.TermsOfService); }
/// <summary> /// Creates an account. /// </summary> /// <param name="context">The ACME context.</param> /// <param name="email">The email.</param> /// <param name="termsOfServiceAgreed">Set to <c>true</c> to accept the terms of service.</param> /// <returns> /// The account created. /// </returns> public static Task <IAccountContext> NewAccount(this IAcmeContext context, string email, bool termsOfServiceAgreed = false) => context.NewAccount(new[] { $"mailto:{email}" }, termsOfServiceAgreed);