public PendingAuthorization BeginRegistrationAndValidation(CertRequestConfig requestConfig, string identifierAlias, string challengeType = ACMESharpCompat.ACMESharpUtils.CHALLENGE_TYPE_HTTP, string domain = null) { //if no alternative domain specified, use the primary domains as the subject if (domain == null) { domain = requestConfig.PrimaryDomain; } // if (GetIdentifier(identifierAlias) == null) //if an identifier exists for the same dns in vault, remove it to avoid confusion this.DeleteIdentifierByDNS(domain); // ACME service requires international domain names in ascii mode, register the new // identifier with Lets Encrypt var authState = ACMESharpUtils.NewIdentifier(identifierAlias, idnMapping.GetAscii(domain)); var identifier = this.GetIdentifier(identifierAlias, reloadVaultConfig: true); //FIXME: when validating subsequent SAN names in parallel request mode, the identifier is null? if (identifier != null && identifier.Authorization != null && identifier.Authorization.IsPending()) { ACMESharpUtils.CompleteChallenge(identifier.Alias, challengeType, Handler: "manual", Regenerate: true, Repeat: true); //get challenge info ReloadVaultConfig(); identifier = GetIdentifier(identifierAlias); try { //identifier challenge specification is now ready for use to prepare and answer for LetsEncrypt to check var challengeInfo = identifier.Challenges.FirstOrDefault(c => c.Value.Type == challengeType).Value; return(new PendingAuthorization() { Challenge = GetAuthorizeChallengeItemFromAuthChallenge(challengeInfo), Identifier = GetDomainIdentifierItemFromIdentifierInfo(identifier), TempFilePath = "", ExtensionlessConfigCheckedOK = false, LogItems = this.GetActionLogSummary() }); } catch (Exception exp) { LogAction("GetIdentifier", exp.ToString()); //identifier challenge could not be requested this time (FIXME: did we discard it when reloading vault?) return(null); } } else { //identifier is null or already valid (previously authorized) return(new PendingAuthorization() { Challenge = null, Identifier = GetDomainIdentifierItemFromIdentifierInfo(identifier), TempFilePath = "", ExtensionlessConfigCheckedOK = false, LogItems = this.GetActionLogSummary() }); } }
public PendingAuthorization BeginRegistrationAndValidation(CertRequestConfig requestConfig, string identifierAlias, string challengeType = "http-01", string domain = null) { //if no alternative domain specified, use the primary domains as the subject if (domain == null) { domain = requestConfig.PrimaryDomain; } // if (GetIdentifier(identifierAlias) == null) //if an identifier exists for the same dns in vault, remove it to avoid confusion this.DeleteIdentifierByDNS(domain); // ACME service requires international domain names in ascii mode, regiser the new identifier with Lets Encrypt var authState = ACMESharpUtils.NewIdentifier(identifierAlias, idnMapping.GetAscii(domain)); var identifier = this.GetIdentifier(identifierAlias, reloadVaultConfig: true); //FIXME: when validating subsequent SAN names in parallel request mode, the identifier is null? if (identifier != null && identifier.Authorization != null && identifier.Authorization.IsPending()) { ACMESharpUtils.CompleteChallenge(identifier.Alias, challengeType, Handler: "manual", Regenerate: true, Repeat: true); //get challenge info ReloadVaultConfig(); identifier = GetIdentifier(identifierAlias); var challengeInfo = identifier.Challenges.FirstOrDefault(c => c.Value.Type == challengeType).Value; //identifier challenege specification is now ready for use to prepare and answer for LetsEncrypt to check return(new PendingAuthorization() { Challenge = challengeInfo, Identifier = identifier, TempFilePath = "", ExtensionlessConfigCheckedOK = false }); } else { //identifier is null or already valid (previously authorized) return(new PendingAuthorization() { Challenge = null, Identifier = identifier, TempFilePath = "", ExtensionlessConfigCheckedOK = false }); } }
public PendingAuthorization PerformIISAutomatedChallengeResponse(CertRequestConfig requestConfig, PendingAuthorization pendingAuth) { bool extensionlessConfigOK = false; bool checkViaProxy = true; //if copying the file for the user, attempt that now if (pendingAuth.Challenge != null && requestConfig.PerformChallengeFileCopy) { var httpChallenge = (ACMESharp.ACME.HttpChallenge)pendingAuth.Challenge.Challenge; this.LogAction("Preparing challenge response for LetsEncrypt server to check at: " + httpChallenge.FileUrl); this.LogAction("If the challenge response file is not accessible at this exact URL the validation will fail and a certificate will not be issued."); //copy temp file to path challenge expects in web folder var destFile = Path.Combine(requestConfig.WebsiteRootPath, httpChallenge.FilePath); var destPath = Path.GetDirectoryName(destFile); if (!Directory.Exists(destPath)) { Directory.CreateDirectory(destPath); } //copy challenge response to web folder /.well-known/acme-challenge System.IO.File.WriteAllText(destFile, httpChallenge.FileContent); var wellknownContentPath = httpChallenge.FilePath.Substring(0, httpChallenge.FilePath.LastIndexOf("/")); var testFilePath = Path.Combine(requestConfig.WebsiteRootPath, wellknownContentPath + "//configcheck"); System.IO.File.WriteAllText(testFilePath, "Extensionless File Config Test - OK"); //create a web.config for extensionless files, then test it (make a request for the extensionless configcheck file over http) string webConfigContent = Properties.Resources.IISWebConfig; if (!File.Exists(destPath + "\\web.config")) { //no existing config, attempt auto config and perform test System.IO.File.WriteAllText(destPath + "\\web.config", webConfigContent); if (requestConfig.PerformExtensionlessConfigChecks) { if (CheckURL("http://" + requestConfig.PrimaryDomain + "/" + wellknownContentPath + "/configcheck", checkViaProxy)) { extensionlessConfigOK = true; } } } else { //web config already exists, don't overwrite it, just test it if (requestConfig.PerformExtensionlessConfigChecks) { if (CheckURL("http://" + requestConfig.PrimaryDomain + "/" + wellknownContentPath + "/configcheck", checkViaProxy)) { extensionlessConfigOK = true; } if (!extensionlessConfigOK && requestConfig.PerformAutoConfig) { //didn't work, try our default config System.IO.File.WriteAllText(destPath + "\\web.config", webConfigContent); if (CheckURL("http://" + requestConfig.PrimaryDomain + "/" + wellknownContentPath + "/configcheck", checkViaProxy)) { extensionlessConfigOK = true; } } } } if (!extensionlessConfigOK && requestConfig.PerformAutoConfig) { //if first attempt(s) at config failed, try an alternative config webConfigContent = Properties.Resources.IISWebConfigAlt; System.IO.File.WriteAllText(destPath + "\\web.config", webConfigContent); if (CheckURL("http://" + requestConfig.PrimaryDomain + "/" + wellknownContentPath + "/configcheck", checkViaProxy)) { //ready to complete challenge extensionlessConfigOK = true; } } } //configuration applied, ready to ask LE to validate our answer pendingAuth.ExtensionlessConfigCheckedOK = extensionlessConfigOK; return(pendingAuth); }
public PendingAuthorization BeginRegistrationAndValidation(CertRequestConfig requestConfig, string identifierAlias, string challengeType = "http-01", string domain = null) { //if no alternative domain specified, use the primary domains as the subject if (domain == null) { domain = requestConfig.PrimaryDomain; } if (GetIdentifier(identifierAlias) == null) { //if an identifier exists for the same dns in vault, remove it to avoid confusion this.DeleteIdentifierByDNS(domain); // ACME service requires international domain names in ascii mode if (UsePowershell) { var result = powershellManager.NewIdentifier(idnMapping.GetAscii(domain), identifierAlias, "Identifier:" + domain); if (!result.IsOK) { return(null); } } else { var cmd = new ACMESharp.POSH.NewIdentifier(); cmd.Dns = idnMapping.GetAscii(domain); cmd.Alias = identifierAlias; cmd.Label = "Identifier:" + domain; try { cmd.ExecuteCommand(); } catch (Exception exp) { this.LogAction("NewIdentifier", exp.ToString()); return(null); } } } var identifier = this.GetIdentifier(identifierAlias, reloadVaultConfig: true); if (identifier.Authorization.IsPending()) { bool ccrResultOK = false; if (UsePowershell) { var result = powershellManager.CompleteChallenge(identifier.Alias, challengeType, regenerate: true); ccrResultOK = result.IsOK; } else { var cmd = new ACMESharp.POSH.CompleteChallenge(); cmd.IdentifierRef = identifier.Alias; cmd.ChallengeType = challengeType; cmd.Handler = "manual"; cmd.Regenerate = new System.Management.Automation.SwitchParameter(true); cmd.Repeat = new System.Management.Automation.SwitchParameter(true); cmd.ExecuteCommand(); ccrResultOK = true; } //get challenge info ReloadVaultConfig(); identifier = GetIdentifier(identifierAlias); var challengeInfo = identifier.Challenges.FirstOrDefault(c => c.Value.Type == challengeType).Value; //identifier challenege specification is now ready for use to prepare and answer for LetsEncrypt to check return(new PendingAuthorization() { Challenge = challengeInfo, Identifier = identifier, TempFilePath = "", ExtensionlessConfigCheckedOK = false }); } else { //identifier is already valid (previously authorized) return(new PendingAuthorization() { Challenge = null, Identifier = identifier, TempFilePath = "", ExtensionlessConfigCheckedOK = false }); } }
public PendingAuthorization BeginRegistrationAndValidation(CertRequestConfig requestConfig, string identifierAlias, string challengeType = ACMESharpCompat.ACMESharpUtils.CHALLENGE_TYPE_HTTP, string domain = null) { //if no alternative domain specified, use the primary domains as the subject if (domain == null) { domain = requestConfig.PrimaryDomain; } // if (GetIdentifier(identifierAlias) == null) //if an identifier exists for the same dns in vault, remove it to avoid confusion this.DeleteIdentifierByDNS(domain); // ACME service requires international domain names in ascii mode, create new identifier // in vault try { var authState = ACMESharpUtils.NewIdentifier(identifierAlias, idnMapping.GetAscii(domain)); } catch (ACMESharp.AcmeClient.AcmeWebException exp) { //if we don't know the problem details, report the whole exception if (exp.Response?.ProblemDetail == null) { throw exp; } // failed to register the domain identifier with LE (invalid, rate limit or CAA fail?) LogAction("NewIdentifier [" + domain + "]", exp.Response.ProblemDetail.OrignalContent); return(new PendingAuthorization { AuthorizationError = $"{exp.Response.ProblemDetail.Detail} : {exp.Response.ProblemDetail.Type}" }); } catch (Exception exp) { // failed to register the domain identifier with LE (rate limit or CAA fail?) LogAction("NewIdentifier [" + domain + "]", exp.ToString()); return(new PendingAuthorization { AuthorizationError = exp.ToString() }); } Thread.Sleep(200); var identifier = this.GetIdentifier(identifierAlias, reloadVaultConfig: true); //FIXME: when validating subsequent SAN names in parallel request mode, the identifier is null? if (identifier != null && identifier.Authorization != null && identifier.Authorization.IsPending()) { var authState = ACMESharpUtils.CompleteChallenge(identifier.Alias, challengeType, Handler: "manual", Regenerate: true, Repeat: true); LogAction("CompleteChallenge", authState.Status); //get challenge info for this identifier identifier = GetIdentifier(identifierAlias, reloadVaultConfig: true); try { //identifier challenge specification is now ready for use to prepare and answer for LetsEncrypt to check var challenges = new List <AuthorizationChallengeItem>(); foreach (var c in identifier.Challenges) { if (c.Value.Type == SupportedChallengeTypes.CHALLENGE_TYPE_HTTP) { var httpChallenge = (ACMESharp.ACME.HttpChallenge)c.Value.Challenge; challenges.Add(new AuthorizationChallengeItem { ChallengeType = SupportedChallengeTypes.CHALLENGE_TYPE_HTTP, ChallengeData = c.Value, ResourcePath = httpChallenge.FilePath, ResourceUri = httpChallenge.FileUrl, Key = c.Value.Token, Value = httpChallenge.FileContent }); } if (c.Value.Type == SupportedChallengeTypes.CHALLENGE_TYPE_SNI) { var tlsSniChallenge = (ACMESharp.ACME.TlsSniChallenge)c.Value.Challenge; var tlsSniAnswer = (ACMESharp.ACME.TlsSniChallengeAnswer)tlsSniChallenge.Answer; challenges.Add(new AuthorizationChallengeItem { ChallengeType = SupportedChallengeTypes.CHALLENGE_TYPE_SNI, ChallengeData = tlsSniChallenge, Key = tlsSniChallenge.Token, Value = tlsSniAnswer.KeyAuthorization, HashIterationCount = tlsSniChallenge.IterationCount }); } //TODO: dns if (c.Value.Type == SupportedChallengeTypes.CHALLENGE_TYPE_DNS) { var dnsChallenge = (ACMESharp.ACME.DnsChallenge)c.Value.Challenge; challenges.Add(new AuthorizationChallengeItem { ChallengeType = SupportedChallengeTypes.CHALLENGE_TYPE_DNS, ChallengeData = dnsChallenge, Key = dnsChallenge.RecordName, Value = dnsChallenge.RecordValue }); } } return(new PendingAuthorization() { Challenges = challenges, Identifier = GetDomainIdentifierItemFromIdentifierInfo(identifier) }); } catch (Exception exp) { //identifier challenge could not be requested this time LogAction("GetIdentifier", exp.ToString()); return(null); } } else { //identifier is null or already valid (previously authorized) return(new PendingAuthorization() { Challenges = null, Identifier = GetDomainIdentifierItemFromIdentifierInfo(identifier), LogItems = this.GetActionLogSummary() }); } }
public PendingAuthorization DomainInitAndRegistration(CertRequestConfig requestConfig, string identifierAlias) { /* * //need to manipulate file created above to set file path or request key sshould be written too. * */ //perform domain cert requests string domain = requestConfig.Domain; // powershellManager.SetWorkingDirectory(this.vaultFolderPath); if (GetIdentifier(identifierAlias) == null) { var result = powershellManager.NewIdentifier(domain, identifierAlias, "Identifier:" + domain); if (!result.IsOK) { return(null); } } ReloadVaultConfig(); var identifier = this.GetIdentifier(identifierAlias); /* * //config file now has a temp path to write to, begin challenge (writes to temp file with challenge content) */ var ccrResult = powershellManager.CompleteChallenge(identifier.Alias, regenerate: true); if (ccrResult.IsOK) { bool extensionlessConfigOK = false; //get challenge info ReloadVaultConfig(); identifier = GetIdentifier(identifierAlias); var challengeInfo = identifier.Challenges.FirstOrDefault(c => c.Value.Type == "http-01").Value; //if copying the file for the user, attempt that now if (challengeInfo != null && requestConfig.PerformChallengeFileCopy) { var httpChallenge = (ACMESharp.ACME.HttpChallenge)challengeInfo.Challenge; //copy temp file to path challenge expects in web folder var destFile = Path.Combine(requestConfig.WebsiteRootPath, httpChallenge.FilePath); var destPath = Path.GetDirectoryName(destFile); if (!Directory.Exists(destPath)) { Directory.CreateDirectory(destPath); } //copy challenge response to web folder /.well-known/acme-challenge System.IO.File.WriteAllText(destFile, httpChallenge.FileContent); var wellknownContentPath = httpChallenge.FilePath.Substring(0, httpChallenge.FilePath.LastIndexOf("/")); var testFilePath = Path.Combine(requestConfig.WebsiteRootPath, wellknownContentPath + "//configcheck"); System.IO.File.WriteAllText(testFilePath, "Extensionless File Config Test - OK"); //create a web.config for extensionless files string webConfigContent = Properties.Resources.IISWebConfig; if (!File.Exists(destPath + "\\web.config")) { System.IO.File.WriteAllText(destPath + "\\web.config", webConfigContent); } if (CheckURL("http://" + domain + "/" + wellknownContentPath + "/configcheck")) { extensionlessConfigOK = true; } if (!extensionlessConfigOK) { webConfigContent = Properties.Resources.IISWebConfigAlt; System.IO.File.WriteAllText(destPath + "\\web.config", webConfigContent); } if (CheckURL("http://" + domain + "/" + wellknownContentPath + "/configcheck")) { extensionlessConfigOK = true; } //ready to complete challenge } return(new PendingAuthorization() { Challenge = challengeInfo, Identifier = identifier, TempFilePath = "", ExtensionlessConfigCheckedOK = extensionlessConfigOK }); } else { return(null); } }
public PendingAuthorization BeginRegistrationAndValidation(CertRequestConfig requestConfig, string identifierAlias) { string domain = requestConfig.Domain; if (GetIdentifier(identifierAlias) == null) { //if an identifier exists for the same dns in vault, remove it to avoid confusion this.DeleteIdentifierByDNS(domain); var result = powershellManager.NewIdentifier(domain, identifierAlias, "Identifier:" + domain); if (!result.IsOK) { return(null); } } var identifier = this.GetIdentifier(identifierAlias, reloadVaultConfig: true); /* * //config file now has a temp path to write to, begin challenge (writes to temp file with challenge content) */ if (identifier.Authorization.IsPending()) { var ccrResult = powershellManager.CompleteChallenge(identifier.Alias, regenerate: true); if (ccrResult.IsOK) { bool extensionlessConfigOK = false; bool checkViaProxy = true; //get challenge info ReloadVaultConfig(); identifier = GetIdentifier(identifierAlias); var challengeInfo = identifier.Challenges.FirstOrDefault(c => c.Value.Type == "http-01").Value; //if copying the file for the user, attempt that now if (challengeInfo != null && requestConfig.PerformChallengeFileCopy) { var httpChallenge = (ACMESharp.ACME.HttpChallenge)challengeInfo.Challenge; this.LogAction("Preparing challenge response for LetsEncrypt server to check at: " + httpChallenge.FileUrl); this.LogAction("If the challenge response file is not accessible at this exact URL the validation will fail and a certificate will not be issued."); //copy temp file to path challenge expects in web folder var destFile = Path.Combine(requestConfig.WebsiteRootPath, httpChallenge.FilePath); var destPath = Path.GetDirectoryName(destFile); if (!Directory.Exists(destPath)) { Directory.CreateDirectory(destPath); } //copy challenge response to web folder /.well-known/acme-challenge System.IO.File.WriteAllText(destFile, httpChallenge.FileContent); var wellknownContentPath = httpChallenge.FilePath.Substring(0, httpChallenge.FilePath.LastIndexOf("/")); var testFilePath = Path.Combine(requestConfig.WebsiteRootPath, wellknownContentPath + "//configcheck"); System.IO.File.WriteAllText(testFilePath, "Extensionless File Config Test - OK"); //create a web.config for extensionless files, then test it (make a request for the extensionless configcheck file over http) string webConfigContent = Properties.Resources.IISWebConfig; if (!File.Exists(destPath + "\\web.config")) { //no existing config, attempt auto config and perform test System.IO.File.WriteAllText(destPath + "\\web.config", webConfigContent); if (requestConfig.PerformExtensionlessConfigChecks) { if (CheckURL("http://" + domain + "/" + wellknownContentPath + "/configcheck", checkViaProxy)) { extensionlessConfigOK = true; } } } else { //web config already exists, don't overwrite it, just test it if (requestConfig.PerformExtensionlessConfigChecks) { if (CheckURL("http://" + domain + "/" + wellknownContentPath + "/configcheck", checkViaProxy)) { extensionlessConfigOK = true; } if (!extensionlessConfigOK && requestConfig.PerformExtensionlessAutoConfig) { //didn't work, try our default config System.IO.File.WriteAllText(destPath + "\\web.config", webConfigContent); if (CheckURL("http://" + domain + "/" + wellknownContentPath + "/configcheck", checkViaProxy)) { extensionlessConfigOK = true; } } } } if (!extensionlessConfigOK && requestConfig.PerformExtensionlessAutoConfig) { //if first attempt(s) at config failed, try an alternative config webConfigContent = Properties.Resources.IISWebConfigAlt; System.IO.File.WriteAllText(destPath + "\\web.config", webConfigContent); if (CheckURL("http://" + domain + "/" + wellknownContentPath + "/configcheck", checkViaProxy)) { //ready to complete challenge extensionlessConfigOK = true; } } } return(new PendingAuthorization() { Challenge = challengeInfo, Identifier = identifier, TempFilePath = "", ExtensionlessConfigCheckedOK = extensionlessConfigOK }); } else { return(null); } } else { //identifier is already valid (previously authorized) return(new PendingAuthorization() { Challenge = null, Identifier = identifier, TempFilePath = "", ExtensionlessConfigCheckedOK = false }); } }