Пример #1
0
        public AccountDetails(ACMESharp.Protocol.AccountDetails accountDetails, IJwsTool clientSigner)
        {
            Kid     = accountDetails.Kid;
            TosLink = accountDetails.TosLink;

            Account accountPayload = accountDetails.Payload;

            if (null == accountPayload)
            {
                throw new ArgumentNullException("accountDetails.Payload");
            }

            Id      = accountPayload.Id;
            Contact = accountPayload.Contact;
            // test server returns testing in status
            if (Enum.TryParse <AccountStatus>(accountPayload.Status, true, out AccountStatus accountStatus))
            {
                Status = accountStatus;
            }

            TermsOfServiceAgreed = accountPayload.TermsOfServiceAgreed ?? false;
            OrdersUrl            = accountPayload.Orders;
            InitialIp            = accountPayload.InitialIp;
            CreatedAt            = accountPayload.CreatedAt;
            Agreement            = accountPayload.Agreement;

            KeyType   = clientSigner.JwsAlg;
            KeyExport = clientSigner.Export();
        }
Пример #2
0
        // TODO: handle "Change of TOS" error response
        //    https://tools.ietf.org/html/draft-ietf-acme-acme-12#section-7.3.4


        /// <summary>
        /// Rotates the current Public key that is associated with this Account by the
        /// target ACME CA with a new Public key.  If successful, updates the current
        /// Account key pair registered with the client.
        /// </summary>
        /// <remarks>
        /// https://tools.ietf.org/html/draft-ietf-acme-acme-18#section-7.3.5
        /// </remarks>
        public async Task <AccountDetails> ChangeAccountKeyAsync(IJwsTool newSigner,
                                                                 CancellationToken cancel = default(CancellationToken))
        {
            if (Account == null)
            {
                Signer = newSigner;
                return(null);
            }

            var requUrl = new Uri(_http.BaseAddress, Directory.KeyChange);
            var message = new KeyChangeRequest
            {
                Account = Account.Kid,
                OldKey  = Signer.ExportJwk(),
            };
            var innerPayload = ComputeAcmeSigned(message, requUrl.ToString(),
                                                 signer: newSigner, includePublicKey: true, excludeNonce: true);
            var resp = await SendAcmeAsync(
                requUrl,
                method : HttpMethod.Post,
                message : innerPayload,
                cancel : cancel);

            Signer = newSigner;

            return(await DecodeAccountResponseAsync(resp, existing : Account));
        }
Пример #3
0
        internal async Task ConfigureAcmeClient()
        {
            var httpClient = _proxyService.GetHttpClient();

            httpClient.BaseAddress = _settings.BaseUri;

            _log.Verbose("Loading ACME account signer...");
            IJwsTool signer        = null;
            var      accountSigner = AccountSigner;

            if (accountSigner != null)
            {
                signer = accountSigner.JwsTool();
            }

            _log.Verbose("Constructing ACME protocol client...");
            try
            {
                _client = new AcmeProtocolClient(
                    httpClient,
                    signer: signer,
                    usePostAsGet: _settings.Acme.PostAsGet);
            }
            catch (CryptographicException)
            {
                if (signer == null)
                {
                    // There has been a problem generate a signer for the
                    // new account, possibly because some EC curve is not
                    // on available on the system? So we give it another
                    // shot with a less fancy RSA signer
                    _log.Verbose("First chance error generating new signer, retrying with RSA instead of ECC");
                    signer = new RSJwsTool
                    {
                        KeySize = _settings.Security.RSAKeyBits
                    };
                    signer.Init();
                    _client = new AcmeProtocolClient(
                        httpClient,
                        signer: signer,
                        usePostAsGet: _settings.Acme.PostAsGet);
                }
                else
                {
                    throw;
                }
            }
            _client.BeforeHttpSend = (x, r) => _log.Debug("Send {method} request to {uri}", r.Method, r.RequestUri);
            _client.AfterHttpSend  = (x, r) => _log.Verbose("Request completed with status {s}", r.StatusCode);
            _client.Directory      = await _client.GetDirectoryAsync();

            await _client.GetNonceAsync();

            _client.Account = await LoadAccount(signer);

            if (_client.Account == null)
            {
                throw new Exception("AcmeClient was unable to find or create an account");
            }
        }
Пример #4
0
        /// <summary>
        /// </summary>
        /// <remarks>
        /// https://tools.ietf.org/html/draft-ietf-acme-acme-12#section-8
        /// </remarks>
        public static IChallengeValidationDetails DecodeChallengeValidation(
            Authorization authz, string challengeType, IJwsTool signer)
        {
            var challenge = authz.Challenges.Where(x => x.Type == challengeType)
                            .FirstOrDefault();

            if (challenge == null)
            {
                throw new InvalidOperationException(
                          $"Challenge type [{challengeType}] not found for given Authorization");
            }

            switch (challengeType)
            {
            case Dns01ChallengeValidationDetails.Dns01ChallengeType:
                return(ResolveChallengeForDns01(authz, challenge, signer));

            case Http01ChallengeValidationDetails.Http01ChallengeType:
                return(ResolveChallengeForHttp01(authz, challenge, signer));

            case TlsAlpn01ChallengeValidationDetails.TlsAlpn01ChallengeType:
                return(ResolveChallengeForTlsAlpn01(authz, challenge, signer));
            }

            throw new NotImplementedException(
                      $"Unknown or unsupported Challenge type [{challengeType}]");
        }
Пример #5
0
 /// <summary>
 /// Computes the ACME Key Authorization of the JSON Web Key (JWK) of an argument
 /// Signer as prescribed in the
 /// <see href="https://tools.ietf.org/html/draft-ietf-acme-acme-01#section-7.1"
 /// >ACME specification, section 7.1</see>.
 /// </summary>
 public static string ComputeKeyAuthorization(IJwsTool signer, string token)
 {
     using (var sha = SHA256.Create())
     {
         var jwkThumb = CryptoHelper.Base64.UrlEncode(ComputeThumbprint(signer, sha));
         return($"{token}.{jwkThumb}");
     }
 }
 public AcmeProtocolClient(HttpClient http, ServiceDirectory dir = null,
                           AccountDetails acct    = null, IJwsTool signer = null,
                           bool disposeHttpClient = false,
                           ILogger logger         = null)
 {
     Init(http, dir, acct, signer, logger);
     _disposeHttpClient = disposeHttpClient;
 }
Пример #7
0
 /// <summary>
 /// Computes a SHA256 digest over the <see cref="ComputeKeyAuthorization"
 /// >ACME Key Authorization</see> as required by some of the ACME Challenge
 /// responses.
 /// </summary>
 public static string ComputeKeyAuthorizationDigest(IJwsTool signer, string token)
 {
     using (var sha = SHA256.Create())
     {
         var jwkThumb    = CryptoHelper.Base64.UrlEncode(ComputeThumbprint(signer, sha));
         var keyAuthz    = $"{token}.{jwkThumb}";
         var keyAuthzDig = sha.ComputeHash(Encoding.UTF8.GetBytes(keyAuthz));
         return(CryptoHelper.Base64.UrlEncode(keyAuthzDig));
     }
 }
Пример #8
0
        /// <summary>
        /// </summary>
        /// <remarks>
        /// https://tools.ietf.org/html/draft-ietf-acme-tls-alpn-05
        /// </remarks>
        public static TlsAlpn01ChallengeValidationDetails ResolveChallengeForTlsAlpn01(
            Authorization authz, Challenge challenge, IJwsTool signer)
        {
            var keyAuthz = JwsHelper.ComputeKeyAuthorization(signer, challenge.Token);

            return(new TlsAlpn01ChallengeValidationDetails
            {
                TokenValue = keyAuthz,
            });
        }
Пример #9
0
        private async Task <AccountDetails> LoadAccount(IJwsTool signer)
        {
            AccountDetails account = null;

            if (File.Exists(AccountPath))
            {
                if (signer != null)
                {
                    _log.Debug("Loading account information from {registrationPath}", AccountPath);
                    account         = JsonConvert.DeserializeObject <AccountDetails>(File.ReadAllText(AccountPath));
                    _client.Account = account;
                    // Maybe we should update the account details
                    // on every start of the program to figure out
                    // if it hasn't been suspended or cancelled?
                    // UpdateAccount();
                }
                else
                {
                    _log.Error("Account found but no valid signer could be loaded");
                }
            }
            else
            {
                var contacts = await GetContacts();

                var(_, filename, content) = await _client.GetTermsOfServiceAsync();

                if (!_arguments.MainArguments.AcceptTos)
                {
                    var tosPath = Path.Combine(_settings.Client.ConfigurationPath, filename);
                    File.WriteAllBytes(tosPath, content);
                    _input.Show($"Terms of service", tosPath);
                    if (await _input.PromptYesNo($"Open in default application?", false))
                    {
                        Process.Start(tosPath);
                    }

                    if (!await _input.PromptYesNo($"Do you agree with the terms?", true))
                    {
                        return(null);
                    }
                }
                account = await _client.CreateAccountAsync(contacts, termsOfServiceAgreed : true);

                _log.Debug("Saving registration");
                var accountKey = new AccountSigner
                {
                    KeyType   = _client.Signer.JwsAlg,
                    KeyExport = _client.Signer.Export(),
                };
                AccountSigner = accountKey;
                File.WriteAllText(AccountPath, JsonConvert.SerializeObject(account));
            }
            return(account);
        }
        public AcmeProtocolClient(Uri baseUri, ServiceDirectory dir = null,
                                  AccountDetails acct = null, IJwsTool signer = null,
                                  ILogger logger      = null)
        {
            var http = new HttpClient
            {
                BaseAddress = baseUri,
            };

            Init(http, dir, acct, signer, logger);
            _disposeHttpClient = true;
        }
Пример #11
0
        /// <summary>
        /// Computes a thumbprint of the JWK using the argument Hash Algorithm
        /// as per <see href="https://tools.ietf.org/html/rfc7638">RFC 7638</see>,
        /// JSON Web Key (JWK) Thumbprint.
        /// </summary>
        public static byte[] ComputeThumbprint(IJwsTool signer, HashAlgorithm algor)
        {
            // As per RFC 7638 Section 3, we export the JWK in a canonical form
            // and then produce a JSON object with no whitespace or line breaks

            var jwkCanon = signer.ExportJwk(true);
            var jwkJson  = JsonConvert.SerializeObject(jwkCanon, Formatting.None);
            var jwkBytes = Encoding.UTF8.GetBytes(jwkJson);
            var jwkHash  = algor.ComputeHash(jwkBytes);

            return(jwkHash);
        }
        /// <summary>
        /// </summary>
        /// <remarks>
        /// https://tools.ietf.org/html/draft-ietf-acme-acme-12#section-8.4
        /// </remarks>
        public static Dns01ChallengeValidationDetails ResolveChallengeForDns01(
            Authorization authz, Challenge challenge, IJwsTool signer)
        {
            var keyAuthzDigested = JwsHelper.ComputeKeyAuthorizationDigest(
                signer, challenge.Token);

            return(new Dns01ChallengeValidationDetails
            {
                DnsRecordName = $@"{Dns01ChallengeValidationDetails.DnsRecordNamePrefix}.{
                        authz.Identifier.Value}",
                DnsRecordType = Dns01ChallengeValidationDetails.DnsRecordTypeDefault,
                DnsRecordValue = keyAuthzDigested,
            });
        }
        private void Init(HttpClient http, ServiceDirectory dir,
                          AccountDetails acct, IJwsTool signer,
                          ILogger logger)
        {
            _http     = http;
            Directory = dir ?? new ServiceDirectory();

            Account = acct;

            Signer = signer ?? ResolveDefaultSigner();

            _log = logger ?? NullLogger.Instance;
            _log.LogInformation("ACME client initialized");
        }
Пример #14
0
        private async Task <AccountDetails> LoadAccount(IJwsTool signer)
        {
            AccountDetails account = null;

            if (File.Exists(AccountPath))
            {
                if (signer != null)
                {
                    _log.Debug("Loading account information from {registrationPath}", AccountPath);
                    account = JsonConvert.DeserializeObject <AccountDetails>(File.ReadAllText(AccountPath));
                }
                else
                {
                    _log.Error("Account found but no valid Signer could be loaded");
                }
            }
            else
            {
                var contacts = GetContacts();
                var(contentType, filename, content) = await _client.GetTermsOfServiceAsync();

                if (!_optionsService.MainArguments.AcceptTos)
                {
                    var tosPath = Path.Combine(_settings.ConfigPath, filename);
                    File.WriteAllBytes(tosPath, content);
                    _input.Show($"Terms of service", tosPath);
                    if (_input.PromptYesNo($"Open in default application?"))
                    {
                        Process.Start(tosPath);
                    }
                    if (!_input.PromptYesNo($"Do you agree with the terms?"))
                    {
                        return(null);
                    }
                }
                account = await _client.CreateAccountAsync(contacts, termsOfServiceAgreed : true);

                _log.Debug("Saving registration");
                var accountKey = new AccountSigner
                {
                    KeyType   = _client.Signer.JwsAlg,
                    KeyExport = _client.Signer.Export(),
                };
                AccountSigner = accountKey;
                File.WriteAllText(AccountPath, JsonConvert.SerializeObject(account));
            }
            return(account);
        }
        /// <summary>
        /// </summary>
        /// <remarks>
        /// https://tools.ietf.org/html/draft-ietf-acme-acme-12#section-8.3
        /// </remarks>
        public static Http01ChallengeValidationDetails ResolveChallengeForHttp01(
            Authorization authz, Challenge challenge, IJwsTool signer)
        {
            var keyAuthz = JwsHelper.ComputeKeyAuthorization(
                signer, challenge.Token);

            return(new Http01ChallengeValidationDetails
            {
                HttpResourceUrl = $@"http://{authz.Identifier.Value}/{
                        Http01ChallengeValidationDetails.HttpPathPrefix}/{
                        challenge.Token}",
                HttpResourcePath = $@"{Http01ChallengeValidationDetails.HttpPathPrefix}/{
                        challenge.Token}",
                HttpResourceContentType = Http01ChallengeValidationDetails.HttpResourceContentTypeDefault,
                HttpResourceValue = keyAuthz,
            });
        }
Пример #16
0
        private void Init(HttpClient http, ServiceDirectory dir,
                          AccountDetails acct, IJwsTool signer,
                          ILogger logger)
        {
            _http     = http;
            Directory = dir ?? new ServiceDirectory();

            Account = acct;

            // We default to ES256 signer
            if (signer == null)
            {
                signer = new Crypto.JOSE.Impl.ESJwsTool();
                signer.Init();
            }
            Signer = signer;

            _log = logger ?? NullLogger.Instance;
            _log.LogInformation("ACME client initialized");
        }
Пример #17
0
        private async Task <bool> ConfigureAcmeClient()
        {
            var httpClientHandler = new HttpClientHandler()
            {
                Proxy = _proxyService.GetWebProxy(),
            };
            var httpClient = new HttpClient(httpClientHandler)
            {
                BaseAddress = new Uri(_arguments.MainArguments.GetBaseUri())
            };

            _log.Verbose("Loading ACME account signer...");
            IJwsTool signer        = null;
            var      accountSigner = AccountSigner;

            if (accountSigner != null)
            {
                signer = accountSigner.JwsTool();
            }

            _log.Verbose("Constructing ACME protocol client...");
            _client = new AcmeProtocolClient(httpClient, signer: signer)
            {
                BeforeHttpSend = (x, r) =>
                {
                    _log.Debug("Send {method} request to {uri}", r.Method, r.RequestUri);
                },
            };

            _client.Directory = await _client.GetDirectoryAsync();

            await _client.GetNonceAsync();

            _client.Account = await LoadAccount(signer);

            if (_client.Account == null)
            {
                throw new Exception("AcmeClient was unable to find or create an account");
            }
            return(true);
        }
Пример #18
0
        void ValidateAccount(DbAccount acct, JwsSignedPayload signedPayload)
        {
            var ph  = ExtractProtectedHeader(signedPayload);
            var jwk = JsonConvert.DeserializeObject <Dictionary <string, string> >(acct.Jwk);

            if (string.IsNullOrEmpty(ph.Alg))
            {
                throw new Exception("invalid JWS header, missing 'alg'");
            }
            if (string.IsNullOrEmpty(ph.Url))
            {
                throw new Exception("invalid JWS header, missing 'url'");
            }
            if (string.IsNullOrEmpty(ph.Nonce))
            {
                throw new Exception("invalid JWS header, missing 'nonce'");
            }

            IJwsTool tool = null;

            switch (ph.Alg)
            {
            case "RS256":
                tool = new RSJwsTool {
                    HashSize = 256
                };
                ((RSJwsTool)tool).ImportJwk(acct.Jwk);
                break;

            case "RS384":
                tool = new RSJwsTool {
                    HashSize = 384
                };
                ((RSJwsTool)tool).ImportJwk(acct.Jwk);
                break;

            case "RS512":
                tool = new RSJwsTool {
                    HashSize = 512
                };
                ((RSJwsTool)tool).ImportJwk(acct.Jwk);
                break;

            case "ES256":
                tool = new ESJwsTool {
                    HashSize = 256
                };
                break;

            case "ES384":
                tool = new ESJwsTool {
                    HashSize = 384
                };
                break;

            case "ES512":
                tool = new ESJwsTool {
                    HashSize = 512
                };
                break;

            default:
                throw new Exception("unknown or unsupported signature algorithm");
            }

            var sig = CryptoHelper.Base64.UrlDecode(signedPayload.Signature);
            var pld = CryptoHelper.Base64.UrlDecode(signedPayload.Payload);
            var prt = CryptoHelper.Base64.UrlDecode(signedPayload.Protected);

            var sigInput      = $"{signedPayload.Protected}.{signedPayload.Payload}";
            var sigInputBytes = Encoding.ASCII.GetBytes(sigInput);

            if (!tool.Verify(sigInputBytes, sig))
            {
                throw new Exception("account signature failure");
            }
        }
Пример #19
0
        private async Task <bool> ConfigureAcmeClient()
        {
            var httpClientHandler = new HttpClientHandler()
            {
                Proxy = _proxyService.GetWebProxy(),
            };
            var httpClient = new HttpClient(httpClientHandler)
            {
                BaseAddress = new Uri(_arguments.MainArguments.GetBaseUri())
            };

            _log.Verbose("Loading ACME account signer...");
            IJwsTool signer        = null;
            var      accountSigner = AccountSigner;

            if (accountSigner != null)
            {
                signer = accountSigner.JwsTool();
            }

            _log.Verbose("Constructing ACME protocol client...");
            try
            {
                _client = new AcmeProtocolClient(httpClient, signer: signer);
            }
            catch (CryptographicException)
            {
                if (signer == null)
                {
                    // There has been a problem generate a signer for the
                    // new account, possibly because some EC curve is not
                    // on available on the system? So we give it another
                    // shot with a less fancy RSA signer
                    _log.Verbose("First chance error generating new signer, retrying with RSA instead of ECC");
                    signer = new RSJwsTool {
                        KeySize = new Rsa(_log, new RsaOptions()).GetRsaKeyBits()
                    };
                    signer.Init();
                    _client = new AcmeProtocolClient(httpClient, signer: signer);
                }
                else
                {
                    throw;
                }
            }
            _client.BeforeHttpSend = (x, r) =>
            {
                _log.Debug("Send {method} request to {uri}", r.Method, r.RequestUri);
            };
            _client.AfterHttpSend = (x, r) =>
            {
                _log.Verbose("Request completed with status {s}", r.StatusCode);
            };
            _client.Directory = await _client.GetDirectoryAsync();

            await _client.GetNonceAsync();

            _client.Account = await LoadAccount(signer);

            if (_client.Account == null)
            {
                throw new Exception("AcmeClient was unable to find or create an account");
            }
            return(true);
        }
Пример #20
0
 public AccountSigner(IJwsTool source)
 {
     KeyType   = source.JwsAlg;
     KeyExport = source.Export();
 }