예제 #1
0
        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}");
                }
            }
        }
예제 #2
0
        /// <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);
        }
예제 #3
0
        private async Task UseExistingLetsEncryptAccount(IKey existingAccountKey)
        {
            _logger.LogDebug("Using existing LetsEncrypt account.");

            acme = new AcmeContext(LetsEncryptUri, existingAccountKey);
            await acme.Account();
        }
예제 #4
0
 /// <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;
 }
예제 #5
0
        /// <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));
        }
예제 #6
0
        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);
        }
예제 #8
0
 /// <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>());
 }
예제 #9
0
        /// <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));
        }
예제 #10
0
 /// <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;
 }
예제 #11
0
        /// <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);
        }
예제 #12
0
        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);
        }
예제 #13
0
        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);
        }
예제 #14
0
 /// <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);
예제 #15
0
 /// <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)
 {
 }
예제 #16
0
 /// <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)
 {
 }
예제 #17
0
        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);
            }
        }
예제 #18
0
 public OrderContext(
     IAcmeContext context,
     Uri location)
     : base(context, location)
 {
 }
예제 #19
0
 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));
 }
예제 #21
0
        /// <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);
        }
예제 #22
0
 /// <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);