public async Task IssueWildCardCertificate() { var restApi = new AcmeRestApi(ProtoacmeContants.LETSENCRYPT_STAGING_ENDPOINT); var client = new ProtoacmeClient(restApi); //1. Load up the account and challenge data. AcmeAccount account = AcmeAccount.FromFile(@"c:\temp\account.dat"); AcmeCertificateFulfillmentPromise promise = AcmeCertificateFulfillmentPromise.FromFile(@"c:\temp\promise.dat"); ChallengeCollection challenges = ChallengeCollection.FromFile <DnsChallenge>(@"c:\temp\challenge.dat"); //2. Tell Lets Encrypt to verify our challenge. var startVerifyResult = await client.Challenge.ExecuteChallengeVerification(challenges[0]); AcmeChallengeStatus challengeStatus = null; while (challengeStatus == null || challengeStatus.Status == "pending") { challengeStatus = await client.Challenge.GetChallengeVerificationStatus(challenges[0]); await Task.Delay(3000); } if (challengeStatus.Status != "valid") { throw new Exception($"Failed to validate challenge token"); } //3. Create the CSR CSR csr = CertificateUtility.GenerateCsr(wildCardDns); SaveCRTPrivateKey(csr); //4. Download the certificate var cert = await client.Certificate.DownloadCertificateAsync(account, promise, csr, CertificateType.Cert); //5. Save the certificate using (FileStream fs = new FileStream(@"c:\temp\mycert.cer", FileMode.Create)) { byte[] buffer = cert.Array; fs.Write(buffer, 0, buffer.Length); } }
private void LoadAccountAndChallengeData <TChallengeType>(out AcmeAccount account, out List <TChallengeType> challenges, out AcmeCertificateFulfillmentPromise promise) where TChallengeType : IAcmeChallengeContent { string baseFolder = @"c:\temp"; using (FileStream fs = new FileStream(Path.Combine(baseFolder, "myaccount.acc"), FileMode.Open)) { using (StreamReader sr = new StreamReader(fs)) { var sAccount = sr.ReadToEnd(); var bAccount = Convert.FromBase64String(sAccount); sAccount = Encoding.UTF8.GetString(bAccount); account = JsonConvert.DeserializeObject <AcmeAccount>(sAccount); } } using (FileStream fs = new FileStream(Path.Combine(baseFolder, "challenges.dat"), FileMode.Open)) { using (StreamReader sr = new StreamReader(fs)) { var sChallenges = sr.ReadToEnd(); var bChallenges = Convert.FromBase64String(sChallenges); sChallenges = Encoding.UTF8.GetString(bChallenges); challenges = JsonConvert.DeserializeObject <List <TChallengeType> >(sChallenges); } } using (FileStream fs = new FileStream(Path.Combine(baseFolder, "challengepromise.dat"), FileMode.Open)) { using (StreamReader sr = new StreamReader(fs)) { var sPromise = sr.ReadToEnd(); var bPromise = Convert.FromBase64String(sPromise); sPromise = Encoding.UTF8.GetString(bPromise); promise = JsonConvert.DeserializeObject <AcmeCertificateFulfillmentPromise>(sPromise); } } }
private void SaveAccountAndChallengeData(AcmeAccount account, IEnumerable <IAcmeChallengeContent> challenges, AcmeCertificateFulfillmentPromise promise) { string baseFolder = @"c:\temp"; var serializedAccount = JsonConvert.SerializeObject(account, Formatting.None); serializedAccount = Convert.ToBase64String(Encoding.UTF8.GetBytes(serializedAccount)); using (FileStream fs = new FileStream(Path.Combine(baseFolder, "myaccount.acc"), FileMode.Create)) { byte[] buffer = Encoding.UTF8.GetBytes(serializedAccount); fs.Write(buffer, 0, buffer.Length); } var sChallenges = JsonConvert.SerializeObject(challenges, Formatting.None); sChallenges = Convert.ToBase64String(Encoding.UTF8.GetBytes(sChallenges)); using (FileStream fs = new FileStream(Path.Combine(baseFolder, "challenges.dat"), FileMode.Create)) { byte[] buffer = Encoding.UTF8.GetBytes(sChallenges); fs.Write(buffer, 0, buffer.Length); } var sCertificatePromise = JsonConvert.SerializeObject(promise); sCertificatePromise = Convert.ToBase64String(Encoding.UTF8.GetBytes(sCertificatePromise)); using (FileStream fs = new FileStream(Path.Combine(baseFolder, "challengepromise.dat"), FileMode.Create)) { byte[] buffer = Encoding.UTF8.GetBytes(sCertificatePromise); fs.Write(buffer, 0, buffer.Length); } foreach (var challenge in challenges) { challenge.SaveToFile(Path.Combine(baseFolder, challenge.Token)); } //Create Account Private Key using (TextWriter writer = new StreamWriter(Path.Combine(baseFolder, "account.key"))) { CertificateUtility.ExportRSAPrivateKey(account.SecurityInfo, writer); } }
/// <summary> /// Gets challenges used to verify domain ownership. /// </summary> /// <param name="account">Existing account.</param> /// <param name="acmeCertificateFulfillmentPromise">The certificate fulfillment promise retrieved from the RequestCertificate call.</param> /// <param name="challengeType">The challenge type expected back.</param> /// <returns>Challenge used to verify domain ownership</returns> /// <remarks>If requesting a challenge for a wildcard domain, only dns challenge is supported.</remarks> /// <exception cref="NotSupportedException">If the challenge type is not supported.</exception> /// <exception cref="AcmeProtocolException">On all other Acme related exceptions</exception> public async Task <ChallengeCollection> GetChallengesAsync(AcmeAccount account, AcmeCertificateFulfillmentPromise acmeCertificateFulfillmentPromise, ChallengeType challengeType) { var response = await _acmeApi.GetChallengesAsync(acmeCertificateFulfillmentPromise); var errorResponse = response.Where(t => t.Status == AcmeApiResponseStatus.Error); if (errorResponse.Any()) { throw new AcmeProtocolException(string.Join(" | ", errorResponse.Select(t => t.Message))); } ChallengeCollection challenges = new ChallengeCollection(); foreach (var resp in response) { AcmeChallenge sChallenge = resp.Data.Challenges.FirstOrDefault(t => t.Type.Equals(challengeType.Value)); if (sChallenge == null) { throw new NotSupportedException($"{challengeType.Value} challenge type not supported in this context."); } IAcmeChallengeContent challengeContent = null; switch (challengeType.Value) { case ProtoacmeContants.CHALLENGE_HTTP: challengeContent = new HttpChallenge(account, sChallenge, resp.Data.Identifier?.Value); challenges.Add(challengeContent); break; case ProtoacmeContants.CHALLENGE_DNS: challengeContent = new DnsChallenge(account, sChallenge, resp.Data.Identifier?.Value); challenges.Add(challengeContent); break; case ProtoacmeContants.CHALLENGE_TLS: challengeContent = new TlsChallenge(account, sChallenge, resp.Data.Identifier?.Value); challenges.Add(challengeContent); break; default: break; } } return(challenges); }
/// <summary> /// Downloads the SSL Certificate. /// </summary> /// <param name="completedPromise">The completed certificate fulfillment promise retreived from the FinalizeChallenge call.</param> /// <param name="certificateType">The type of certificate you are requesting.</param> /// <returns>The certificate.</returns> public async Task <AcmeApiResponse <ArraySegment <byte> > > GetCertificateAsync(AcmeCertificateFulfillmentPromise completedPromise, CertificateType certificateType) { if (completedPromise == null) { throw new ArgumentNullException("completedPromise"); } if (string.IsNullOrEmpty(completedPromise.Certificate)) { throw new ArgumentException("Certificate url is not valid"); } if (certificateType == null) { throw new ArgumentNullException("certificateType"); } HttpRequestMessage message = new HttpRequestMessage(HttpMethod.Get, completedPromise.Certificate); message.Headers.Accept.ParseAdd($"application/{certificateType.Value}"); var apiResp = await _httpClient.SendAsync(message); string apiRespString = await apiResp.Content?.ReadAsStringAsync(); if (apiResp.StatusCode != HttpStatusCode.OK) { return(ErrorResponse <ArraySegment <byte> >(apiRespString)); } byte[] bContent = await apiResp.Content.ReadAsByteArrayAsync(); return(new AcmeApiResponse <ArraySegment <byte> >() { Status = AcmeApiResponseStatus.Success, Data = new ArraySegment <byte>(bContent) }); }
/// <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> /// Gets challenges used to verify domain ownership. /// </summary> /// <param name="acmeCertificateFulfillmentPromise">The certificate fulfillment promise retrieved from the RequestCertificate call.</param> /// <returns>An authorization object containing the available challenge types. Wrapped by a response object.</returns> public async Task <List <AcmeApiResponse <AcmeAuthorization> > > GetChallengesAsync(AcmeCertificateFulfillmentPromise acmeCertificateFulfillmentPromise) { List <AcmeApiResponse <AcmeAuthorization> > response = new List <AcmeApiResponse <AcmeAuthorization> >(); if (acmeCertificateFulfillmentPromise == null) { throw new ArgumentNullException("acmeCertificateFulfillmentPromise"); } if (acmeCertificateFulfillmentPromise.Authorizations == null || !acmeCertificateFulfillmentPromise.Authorizations.Any()) { throw new ArgumentException("No Authorizations exist in the Acme Certification Fulfillment Promise"); } foreach (string authUrl in acmeCertificateFulfillmentPromise.Authorizations) { AcmeApiResponse <AcmeAuthorization> result = new AcmeApiResponse <AcmeAuthorization>(); var apiResp = await _httpClient.GetAsync(authUrl); string apiRespString = await apiResp.Content?.ReadAsStringAsync(); if (!apiResp.IsSuccessStatusCode) { result.Status = AcmeApiResponseStatus.Error; result.Message = apiRespString; } else { result.Status = AcmeApiResponseStatus.Success; result.Data = JsonConvert.DeserializeObject <AcmeAuthorization>(apiRespString); } response.Add(result); } return(response); }
/// <summary> /// Downloads the SSL Certificate. /// </summary> /// <param name="account">Existing account</param> /// <param name="completedPromise">The completed certificate fulfillment promise</param> /// <param name="csr">Certificate request</param> /// <param name="certificateType">The type of certificate.</param> /// <returns>The certificate.</returns> public async Task <ArraySegment <byte> > DownloadCertificateAsync(AcmeAccount account, AcmeCertificateFulfillmentPromise completedPromise, Protoacme.Utility.Certificates.CSR csr, CertificateType certificateType) { var directory = await _directoryCache.GetAsync(); var nonce = await _nonceCache.GetAsync(); var finalizeResponse = await _acmeApi.FinalizeCertificatePromiseAsync(account, nonce, completedPromise, csr.Base64UrlEncoded); if (finalizeResponse.Status == AcmeApiResponseStatus.Error) { throw new AcmeProtocolException(finalizeResponse.Message); } var response = await _acmeApi.GetCertificateAsync(finalizeResponse.Data, certificateType); if (response.Status == AcmeApiResponseStatus.Error) { throw new AcmeProtocolException(response.Message); } _nonceCache.Update(finalizeResponse.Nonce); return(response.Data); }