/// <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) }); }
/// <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() }); }
/// <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) }); }
/// <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) }); }
/// <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() }); }
/// <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 } }); }