예제 #1
0
        /// <summary>
        /// Request a certificate.
        /// </summary>
        /// <param name="order">A previously completed order.</param>
        /// <param name="key">The private key to sign the certificate request with.</param>
        /// <param name="cancellationToken">Cancellation token for the async request.</param>
        /// <returns>An updated <see cref="Order"/> object.</returns>
        /// <remarks>The subjectname for the request is the first identifier in <paramref name="order"/>. Subsequent identifiers are added as alternative names.</remarks>
        public async Task <Order> RequestCertificateAsync(Order order, RSACryptoServiceProvider key, CancellationToken cancellationToken = default)
        {
            var csr = new CertificateRequest("CN=" + order.Identifiers[0].Value, key, HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1);

            var san = new SubjectAlternativeNameBuilder();

            foreach (var identifier in order.Identifiers.Skip(1))
            {
                san.AddDnsName(identifier.Value);
            }
            csr.CertificateExtensions.Add(san.Build());

            var message = new FinalizeRequest
            {
                CSR = Jws.Base64UrlEncoded(csr.CreateSigningRequest())
            };

            var(result, responseText) = await client.PostAsync <Order>(order.Finalize, message, cancellationToken);

            if (result is Order acmeOrder)
            {
                return(acmeOrder);
            }

            throw new InvalidServerResponse("Invalid response from server during RequestCertificate.", responseText, order.Finalize.ToString());
        }
예제 #2
0
        public AcmeClient(HttpClient client, RSA key)
        {
            Info($"using server {client.BaseAddress}");

            this.client = client ?? throw new ArgumentNullException(nameof(client));

            jws = new Jws(key);
        }
예제 #3
0
        /// <summary>
        /// Initializes a new instance of the <see cref="ACMERestClient"/> class.
        /// </summary>
        /// <param name="jws">Jws key to use.</param>
        /// <param name="logger">Optional logger.</param>
        public ACMERestClient(Jws jws)
        {
            var client         = typeof(ACMEClient);
            var runtimeVersion = client.Assembly.GetCustomAttribute <AssemblyFileVersionAttribute>();

            this.jws  = jws;
            UserAgent = $"{client.FullName}/{runtimeVersion.Version.ToString()} ({RuntimeInformation.OSDescription} {RuntimeInformation.ProcessArchitecture})";
        }
예제 #4
0
        public LetsEncryptClient(string url)
        {
            _url = url ?? throw new ArgumentNullException(nameof(url));

            var home = Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData,
                                                 Environment.SpecialFolderOption.Create);

            var hash = SHA256.Create().ComputeHash(Encoding.UTF8.GetBytes(url));
            var file = Jws.Base64UrlEncoded(hash) + ".lets-encrypt.cache.json";

            _path = Path.Combine(home, file);
        }
예제 #5
0
        private async Task <JwsEncodedMessage> GetSignedMessageAsync(string url)
        {
            Nonce nonce = await GetNonceAsync();

            var header = GetMessageHeader(url, nonce);

            return(Jws.Sign(
                       integrityProtected: Base64Url.Encode(header),
                       payload: string.Empty,
                       privateKey: _privateKey
                       ));
        }
예제 #6
0
        private async Task <JwsEncodedMessage> GetSignedMessageAsync(string url, JsonObject payload)
        {
            Nonce nonce = await GetNonceAsync();

            var header = GetMessageHeader(url, nonce);

            return(Jws.Sign(
                       integrityProtectedHeader: header,
                       payload: payload,
                       privateKey: _privateKey
                       ));
        }
예제 #7
0
        /// <summary>
        /// Initializes a new instance of the <see cref="ACMEClient"/> class.
        /// </summary>
        /// <param name="endpoint">The selected endpoint of the ACME server.</param>
        /// <param name="rsaKey">Encryption key for account.</param>
        /// <param name="accountId">Account id.</param>
        /// <param name="restClientFactory">An instance of a <see cref="IRESTClient"/> for API requests.</param>
        public ACMEClient(string endpoint, RSA rsaKey, string accountId, IRestClientFactory restClientFactory)
        {
            if (endpoint == null)
            {
                throw new ArgumentNullException(nameof(endpoint));
            }

            this.endpoint = new Uri(endpoint);
            this.rsaKey   = rsaKey ?? throw new ArgumentNullException(nameof(rsaKey));

            jws    = new Jws(rsaKey, accountId);
            client = restClientFactory.CreateRestClient(jws);
        }
예제 #8
0
        public async Task <string> GetDnsChallenge(string hostname, CancellationToken token = default(CancellationToken))
        {
            _hosts.Add(hostname);

            var challenge = await _client.NewDnsAuthorizationAsync(hostname, token);

            var dnsChallenge = challenge.Challenges.First(x => x.Type == "dns-01");
            var keyToken     = _client.GetKeyAuthorization(dnsChallenge.Token);
            var computedDns  = Jws.Base64UrlEncoded(SHA256.Create().ComputeHash(Encoding.UTF8.GetBytes(keyToken)));

            _challenges.Add(dnsChallenge);

            return(computedDns);
        }
예제 #9
0
        public async Task ValidateSignature(Jws jws)
        {
            if (jws.Algorithm == JwsAlgorithm.none)
            {
                return;
            }

            var signedBytes = GetBytes(jws.RawSignedPart);
            var signature   = Base64Url.DeserializeBytes(jws.RawSignature, "Token signature");

            var validator = ValidatorFactory.Create(jws.Header, jws.Algorithm);

            var keys = (await Metadata.JsonWebKeys()).Keys;

            validator.Validate(signedBytes, signature, keys);
        }
예제 #10
0
        // TODO: double check https://tools.ietf.org/html/rfc7515#section-5.2 point 5
        public void ValidateJoseHeader(Jws jws)
        {
            // The JWS Signature value is not valid if the "alg" value does not represent
            // a supported algorithm (https://tools.ietf.org/html/rfc7515#section-4.1.1)
            Logger.ThrowIf(jws.Header.Alg.IsEmpty(), "Missing JWS Algorithm");
            Logger.ThrowIf(jws.Algorithm == JwsAlgorithm.Unknown, "Unknown JWS Algorithm: {0}", jws.Header.Alg);

            // TODO: During this step, verify that the resulting JOSE Header does not contain duplicate Header Parameter names.
            // https://tools.ietf.org/html/rfc7515#section-5.2 - Point 4
            // With the current json implementation identifying these duplications is not possible

            Logger.ThrowIf(jws.Header.Crit != null, "JWS \"crit\" extensions are not supported yet");

            // https://tools.ietf.org/html/rfc7516#section-9
            Logger.ThrowIf(jws.Header.Enc != null, "The JWS header contains \"enc\" key which is used by JWE tokens");
        }
예제 #11
0
            public async Task <CertificateResponse> NewCertificateRequestAsync(byte[] csr, CancellationToken token = default(CancellationToken))
            {
                await EnsureDirectoryAsync().ConfigureAwait(false);

                var request = new AcmeCertificateRequest
                {
                    Csr = Jws.Base64UrlEncoded(csr)
                };

                var response = await PostAsync <CertificateResponse>(
                    _directory.NewCertificate,
                    request,
                    token
                    ).ConfigureAwait(false);

                return(response);
            }
예제 #12
0
        public async Task Init(string email, CancellationToken token = default(CancellationToken))
        {
            _accountKey     = new RSACryptoServiceProvider(4096);
            _client         = GetCachedClient(_url);
            (_directory, _) = await SendAsync <Directory>(HttpMethod.Get, new Uri("directory", UriKind.Relative), null, token);

            // Use this when testing against pebble
            //(_directory, _) = await SendAsync<Directory>(HttpMethod.Get, new Uri("dir", UriKind.Relative), null, token);

            if (File.Exists(_path))
            {
                bool success;
                try
                {
                    lock (Locker)
                    {
                        _cache = JsonConvert.DeserializeObject <RegistrationCache>(File.ReadAllText(_path));
                    }

                    _accountKey.ImportCspBlob(_cache.AccountKey);

                    // From: https://community.letsencrypt.org/t/acme-v2-strict-jws-kid-header-processing/63321
                    // "KeyID headers contain the full account URL as returned by the Location header in a newAccount response"
                    var kid = _cache.Location.ToString();

                    _jws    = new Jws(_accountKey, kid);
                    success = true;
                }
                catch
                {
                    success = false;
                    // if we failed for any reason, we'll just
                    // generate a new registration
                }

                if (success)
                {
                    return;
                }
            }

            _jws = new Jws(_accountKey, null);
            var(account, response) = await SendAsync <Account>(HttpMethod.Post, _directory.NewAccount, new Account
            {
                // we validate this in the UI before we get here, so that is fine
                TermsOfServiceAgreed = true,
                Contacts             = new[] { "mailto:" + email },
            }, token);

            _jws.SetKeyId(account);

            if (account.Status != "valid")
            {
                throw new InvalidOperationException("Account status is not valid, was: " + account.Status + Environment.NewLine + response);
            }

            lock (Locker)
            {
                _cache = new RegistrationCache
                {
                    Location   = account.Location,
                    AccountKey = _accountKey.ExportCspBlob(true),
                    Id         = account.Id,
                    Key        = account.Key
                };
                File.WriteAllText(_path,
                                  JsonConvert.SerializeObject(_cache, Formatting.Indented));
            }
        }
예제 #13
0
            public AcmeClient(HttpClient client, RSA key)
            {
                _client = client ?? throw new ArgumentNullException(nameof(client));

                _jws = new Jws(key);
            }
예제 #14
0
        public void Ensure_key_authorization_is_correct()
        {
            var jws = new Jws(GetPrivateKey());

            Assert.Equal("token.pdmN_UI10XD6wy44jm-JkHmJOFxevse_2jio8cH1lRw", jws.GetKeyAuthorization("token"));
        }
예제 #15
0
 /// <summary>
 /// Creates an instance of <see cref="ACMERestClient"/>.
 /// </summary>
 /// <param name="jws">Jws key used to sign requests.</param>
 /// <returns>An instance of <see cref="ACMERestClient"/></returns>
 public IRestClient CreateRestClient(Jws jws)
 {
     return(new ACMERestClient(jws));
 }