Пример #1
0
        /// <summary>
        /// Sends a new order requesting a new certificate.
        /// </summary>
        /// <param name="directory">Directory object.</param>
        /// <param name="nonce">Nonce</param>
        /// <param name="account">Must be existing account.</param>
        /// <param name="certificates">Info describing the dns entries you are requesting certificates for.</param>
        /// <returns>A certificate fulfillment promise that is used to complete the certification chain in future requests.  Wrapped by a response object.</returns>
        public async Task <AcmeApiResponse <AcmeCertificateFulfillmentPromise> > RequestCertificateAsync(AcmeDirectory directory, string nonce, AcmeAccount account, AcmeCertificateRequest certificates)
        {
            if (directory == null)
            {
                throw new ArgumentNullException("directory");
            }
            if (string.IsNullOrEmpty(directory.NewAccount))
            {
                throw new ArgumentException("directory is missing Account url.");
            }
            if (string.IsNullOrEmpty(nonce))
            {
                throw new ArgumentNullException("nonce");
            }
            if (account == null)
            {
                throw new ArgumentNullException("account");
            }
            if (certificates == null)
            {
                throw new ArgumentNullException("certificates");
            }
            if (certificates.Identifiers == null || !certificates.Identifiers.Any())
            {
                throw new ArgumentException("Certificate is missing identifiers");
            }

            JwsContainer <AcmeCertificateRequest> jwsObject = new JwsContainer <AcmeCertificateRequest>(account.SecurityInfo, nonce, directory.NewOrder, account.KID, certificates);

            string jwsToken = jwsObject.SerializeSignedToken();

            var apiResp = await SendPostData(
                url : directory.NewOrder,
                data : jwsToken);

            string apiRespString = await apiResp.Content?.ReadAsStringAsync();

            if (apiResp.StatusCode != HttpStatusCode.Created)
            {
                return(ErrorResponse <AcmeCertificateFulfillmentPromise>(apiRespString));
            }

            if (!apiResp.Headers.TryGetValues(ProtoacmeContants.HEADER_NONCE, out IEnumerable <string> nonces))
            {
                return(ErrorResponse <AcmeCertificateFulfillmentPromise>("Missing Replay-Nonce Header on RequestCertificate Response."));
            }

            return(new AcmeApiResponse <AcmeCertificateFulfillmentPromise>()
            {
                Status = AcmeApiResponseStatus.Success,
                Nonce = nonces.FirstOrDefault(),
                Data = JsonConvert.DeserializeObject <AcmeCertificateFulfillmentPromise>(apiRespString)
            });
        }
Пример #2
0
        /// <summary>
        /// Updates an existing accounts contacts
        /// </summary>
        /// <param name="directory">Directory object.</param>
        /// <param name="nonce">Nonce</param>
        /// <param name="account">Must be existing account.</param>
        /// <returns>Return api response with status.</returns>
        public async Task <AcmeApiResponse> UpdateAccountAsync(AcmeDirectory directory, string nonce, AcmeAccount account)
        {
            if (directory == null)
            {
                throw new ArgumentNullException("directory");
            }
            if (string.IsNullOrEmpty(directory.NewAccount))
            {
                throw new ArgumentException("directory is missing Account url.");
            }
            if (string.IsNullOrEmpty(nonce))
            {
                throw new ArgumentNullException("nonce");
            }
            if (account == null)
            {
                throw new ArgumentNullException("account");
            }

            AcmeCreateAccount upd = new AcmeCreateAccount()
            {
                Contact = account.Contact, TermsOfServiceAgreed = true
            };

            JwsContainer <AcmeCreateAccount> jwsObject = new JwsContainer <AcmeCreateAccount>(account.SecurityInfo, nonce, account.KID, account.KID, upd);

            string jwsToken = jwsObject.SerializeSignedToken();

            var apiResp = await SendPostData(
                url : _letsEncryptEndpoint.AppendUrl(ProtoacmeContants.LETSENCRYPT_ACCOUNT_FRAGMENT).AppendUrl(account.Id.ToString()),
                data : jwsToken);

            string apiRespString = await apiResp.Content?.ReadAsStringAsync();

            if (apiResp.StatusCode != HttpStatusCode.OK)
            {
                return(ErrorResponse(apiRespString));
            }

            if (!apiResp.Headers.TryGetValues(ProtoacmeContants.HEADER_NONCE, out IEnumerable <string> nonces))
            {
                return(ErrorResponse <AcmeAccount>("Missing Replay-Nonce Header on CreateAccount (UPDATE) Response."));
            }

            return(new AcmeApiResponse()
            {
                Status = AcmeApiResponseStatus.Success,
                Nonce = nonces.FirstOrDefault()
            });
        }
Пример #3
0
        /// <summary>
        /// Finalize certificate request. This allows the certificate to be downloaded by using the GetCertificate request.
        /// </summary>
        /// <param name="account">Must be existing account.</param>
        /// <param name="nonce">Nonce</param>
        /// <param name="acmeCertificateFulfillmentPromise">The original Certificate Fulfillment Promise used in the RequestCertificate request.</param>
        /// <param name="csr">The certificate CSR. This can be generated using helpers through this api or by an external source such as IIS.</param>
        /// <returns>A completed Certificate Fulfillment Promise used to Download the certificate using the GetCertificate call. Wrapped by a response object.</returns>
        public async Task <AcmeApiResponse <AcmeCertificateFulfillmentPromise> > FinalizeCertificatePromiseAsync(AcmeAccount account, string nonce, AcmeCertificateFulfillmentPromise acmeCertificateFulfillmentPromise, string csr)
        {
            if (string.IsNullOrEmpty(nonce))
            {
                throw new ArgumentNullException("nonce");
            }
            if (account == null)
            {
                throw new ArgumentNullException("account");
            }
            if (acmeCertificateFulfillmentPromise == null)
            {
                throw new ArgumentNullException("acmeCertificateFulfillmentPromise");
            }
            if (string.IsNullOrEmpty(csr))
            {
                throw new ArgumentNullException("csr");
            }

            JwsContainer <CSR> jwsObject = new JwsContainer <CSR>(account.SecurityInfo, nonce, acmeCertificateFulfillmentPromise.Finalize, account.KID, new CSR()
            {
                csr = csr
            });

            string jwsToken = jwsObject.SerializeSignedToken();

            var apiResp = await SendPostData(
                url : acmeCertificateFulfillmentPromise.Finalize,
                data : jwsToken);

            string apiRespString = await apiResp.Content?.ReadAsStringAsync();

            if (apiResp.StatusCode != HttpStatusCode.OK)
            {
                return(ErrorResponse <AcmeCertificateFulfillmentPromise>(apiRespString));
            }

            if (!apiResp.Headers.TryGetValues(ProtoacmeContants.HEADER_NONCE, out IEnumerable <string> nonces))
            {
                return(ErrorResponse <AcmeCertificateFulfillmentPromise>("Missing Replay-Nonce Header on FinalizeChallenge Response."));
            }

            return(new AcmeApiResponse <AcmeCertificateFulfillmentPromise>()
            {
                Status = AcmeApiResponseStatus.Success,
                Nonce = nonces.FirstOrDefault(),
                Data = JsonConvert.DeserializeObject <AcmeCertificateFulfillmentPromise>(apiRespString)
            });
        }
Пример #4
0
        /// <summary>
        /// Start the challenge verification process.
        /// </summary>
        /// <param name="account">Must be existing account.</param>
        /// <param name="challenge">Single challenge from the AcmeAuthorization</param>
        /// <param name="nonce">Nonce</param>
        /// <param name="keyAuthorization">Authorization that identifies the domain and user.</param>
        /// <returns>The status of the challenge authorization. Wrapped by a response object.</returns>
        /// <remarks>This will need to be called on each domain that is used in the RequestCertificate call. You should not call this until the challenges are ready to verify. See https://tools.ietf.org/html/draft-ietf-acme-acme-12#section-7.5 for more information.</remarks>
        public async Task <AcmeApiResponse <AcmeChallengeStatus> > VerifyChallengeAsync(AcmeAccount account, AcmeChallenge challenge, string nonce, string keyAuthorization)
        {
            if (string.IsNullOrEmpty(nonce))
            {
                throw new ArgumentNullException("nonce");
            }
            if (account == null)
            {
                throw new ArgumentNullException("account");
            }
            if (challenge == null)
            {
                throw new ArgumentNullException("challenge");
            }
            if (string.IsNullOrEmpty(keyAuthorization))
            {
                throw new ArgumentNullException("keyAuthorization");
            }

            JwsContainer <KEYAUTH> jwsObject = new JwsContainer <KEYAUTH>(account.SecurityInfo, nonce, challenge.Url, account.KID, new KEYAUTH()
            {
                keyAuthorization = keyAuthorization
            });

            string jwsToken = jwsObject.SerializeSignedToken();

            var apiResp = await SendPostData(
                url : challenge.Url,
                data : jwsToken);

            string apiRespString = await apiResp.Content?.ReadAsStringAsync();

            if (apiResp.StatusCode != HttpStatusCode.OK)
            {
                return(ErrorResponse <AcmeChallengeStatus>(apiRespString));
            }

            if (!apiResp.Headers.TryGetValues(ProtoacmeContants.HEADER_NONCE, out IEnumerable <string> nonces))
            {
                return(ErrorResponse <AcmeChallengeStatus>("Missing Replay-Nonce Header on CompleteChallenge Response."));
            }

            return(new AcmeApiResponse <AcmeChallengeStatus>()
            {
                Status = AcmeApiResponseStatus.Success,
                Nonce = nonces.FirstOrDefault(),
                Data = JsonConvert.DeserializeObject <AcmeChallengeStatus>(apiRespString)
            });
        }
Пример #5
0
        /// <summary>
        /// Changes and updates the account security info for an existing account.
        /// </summary>
        /// <param name="directory">Directory object.</param>
        /// <param name="nonce">Nonce</param>
        /// <param name="account">Must be existing account.</param>
        /// <returns>Return api response with status.</returns>
        /// <remarks>Will update the security info on the passed in account, so you will need to reserialize and update your existing account object to update the security info.</remarks>
        public async Task <AcmeApiResponse> RollOverAccountKeyAsync(AcmeDirectory directory, string nonce, AcmeAccount account)
        {
            if (directory == null)
            {
                throw new ArgumentNullException("directory");
            }
            if (string.IsNullOrEmpty(directory.NewAccount))
            {
                throw new ArgumentException("directory is missing Account url.");
            }
            if (string.IsNullOrEmpty(nonce))
            {
                throw new ArgumentNullException("nonce");
            }
            if (account == null)
            {
                throw new ArgumentNullException("account");
            }

            RSACryptoServiceProvider cryptoProvider = new RSACryptoServiceProvider(2048);
            RSAParameters            rsaPrams       = cryptoProvider.ExportParameters(true);

            JwsContainer <ACCKEY> innerJwsObject = new JwsContainer <ACCKEY>(
                rsaPrams,
                nonce,
                directory.KeyChange,
                new ACCKEY()
            {
                account = account.KID,
                newKey  = new JWK()
                {
                    e   = Base64Tool.Encode(rsaPrams.Exponent),
                    kty = "RSA",
                    n   = Base64Tool.Encode(rsaPrams.Modulus)
                }
            });

            object signedInnerJwsObject = innerJwsObject.SerializeSignedObject();

            JwsContainer <object> outerJwsObject = new JwsContainer <object>(account.SecurityInfo, nonce, directory.KeyChange, account.KID, signedInnerJwsObject);

            string jwsToken = outerJwsObject.SerializeSignedToken();

            var apiResp = await SendPostData(
                url : directory.KeyChange,
                data : jwsToken);

            string apiRespString = await apiResp.Content?.ReadAsStringAsync();

            if (apiResp.StatusCode != HttpStatusCode.OK)
            {
                return(ErrorResponse(apiRespString));
            }

            if (!apiResp.Headers.TryGetValues(ProtoacmeContants.HEADER_NONCE, out IEnumerable <string> nonces))
            {
                return(ErrorResponse <AcmeAccount>("Missing Replay-Nonce Header on RolloverKey Response."));
            }

            account.SecurityInfo = rsaPrams;

            return(new AcmeApiResponse()
            {
                Status = AcmeApiResponseStatus.Success,
                Nonce = nonces.FirstOrDefault()
            });
        }
Пример #6
0
        /// <summary>
        /// Creates a new account.
        /// </summary>
        /// <param name="directory">Directory object.</param>
        /// <param name="nonce">Nonce</param>
        /// <param name="account">Information for new account.</param>
        /// <returns>Returns a serializable account object. Wrapped by a response object.</returns>
        /// <remarks>It is best to serialize and save the account object so it can be retrieved later and used for renewing domains.</remarks>
        public async Task <AcmeApiResponse <AcmeAccount> > CreateAccountAsync(AcmeDirectory directory, string nonce, AcmeCreateAccount account)
        {
            if (directory == null)
            {
                throw new ArgumentNullException("directory");
            }
            if (string.IsNullOrEmpty(directory.NewAccount))
            {
                throw new ArgumentException("directory is missing Account url.");
            }
            if (string.IsNullOrEmpty(nonce))
            {
                throw new ArgumentNullException("nonce");
            }
            if (account == null)
            {
                throw new ArgumentNullException("account");
            }

            RSACryptoServiceProvider cryptoProvider = new RSACryptoServiceProvider(2048);
            RSAParameters            rsaPrams       = cryptoProvider.ExportParameters(true);

            JwsContainer <AcmeCreateAccount> jwsObject = new JwsContainer <AcmeCreateAccount>(rsaPrams, nonce, directory.NewAccount, account);

            string jwsToken = jwsObject.SerializeSignedToken();

            var apiResp = await SendPostData(
                url : directory.NewAccount,
                data : jwsToken);

            string apiRespString = await apiResp.Content?.ReadAsStringAsync();

            if (apiResp.StatusCode != HttpStatusCode.Created)
            {
                return(ErrorResponse <AcmeAccount>(apiRespString));
            }

            if (!apiResp.Headers.TryGetValues(ProtoacmeContants.HEADER_LOCATION, out IEnumerable <string> locations))
            {
                return(ErrorResponse <AcmeAccount>("Missing Location Header on CreateAccount Response."));
            }

            if (!apiResp.Headers.TryGetValues(ProtoacmeContants.HEADER_NONCE, out IEnumerable <string> nonces))
            {
                return(ErrorResponse <AcmeAccount>("Missing Replay-Nonce Header on CreateAccount Response."));
            }

            Dictionary <string, object> oResp = JsonConvert.DeserializeObject <Dictionary <string, object> >(apiRespString);

            var loc = locations.FirstOrDefault();
            var id  = int.Parse((new Uri(loc)).Segments.Last());

            return(new AcmeApiResponse <AcmeAccount>()
            {
                Status = AcmeApiResponseStatus.Success,
                Nonce = nonces.FirstOrDefault(),
                Data = new AcmeAccount()
                {
                    Id = id,
                    KID = loc,
                    SecurityInfo = rsaPrams,
                    Contact = account.Contact
                }
            });
        }