diff --git a/CHANGELOG.md b/CHANGELOG.md index 3d38975..aa69eb5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,15 +1,38 @@ -v1.0.4/1.0.5 +v1.1.0 +- Added partial sync functionality +- Added five new optional settings to Gateway config - ApiTimeoutinSeconds, NumberOfCertPageRetrievalRetriesBeforeFailure, NumberOfCertDownloadRetriesBeforeSkip, + NumberOfTimeoutsBeforeSyncFailure, MillisecondsBetweenCertDownloads +- Added logic for certificate download retries for timeouts based on new settings above +- Added additional sync statistics logging for each sync showing number of certificate retrievals, downloads, and any API timeout counts + +v1.0.8 +- Improved logging +- Improved error handling for API timeouts + +v1.0.7 +- Improved logging + +v1.0.6 +- Code cleanup + +v1.0.4/1.0.5 - Update nuget packages -v1.0.3 -- Code cleanup, publish to github. +v1.0.3 +- Code cleanup, publish to github. + +v1.0.2 +- Remove PEM header before returning certificates during sync and enrollment + +v1.0.1 +- Added support for 5 OV and 2 EV GoDaddy products +- Added Renew/Reissue functionality + +v1.0.0: +- Original Version + -v1.0.2 -- Remove PEM header before returning certificates during sync and enrollment + + -v1.0.1 -- Added support for 5 OV and 2 EV GoDaddy products -- Added Renew/Reissue functionality -v1.0.0: -- Original Version diff --git a/GoDaddy.sln b/GoDaddy.sln index cf6ff0c..5a48d55 100644 --- a/GoDaddy.sln +++ b/GoDaddy.sln @@ -8,11 +8,11 @@ EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{5D9941D2-A44F-4593-A33E-F5E0C861CAEB}" ProjectSection(SolutionItems) = preProject .gitignore = .gitignore - CHANGELOG.MD = CHANGELOG.MD + CHANGELOG.md = CHANGELOG.md GoDaddy.sln.licenseheader = GoDaddy.sln.licenseheader integration-manifest.json = integration-manifest.json README.md = README.md - README.md.tpl = README.md.tpl + readme_source.md = readme_source.md EndProjectSection EndProject Global diff --git a/GoDaddy/API/APIProcessor.cs b/GoDaddy/API/APIProcessor.cs index 1258212..74db08c 100644 --- a/GoDaddy/API/APIProcessor.cs +++ b/GoDaddy/API/APIProcessor.cs @@ -1,11 +1,11 @@ // Copyright 2021 Keyfactor -// +// // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at -// +// // http://www.apache.org/licenses/LICENSE-2.0 -// +// // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. @@ -18,237 +18,345 @@ using Newtonsoft.Json; using RestSharp; using System; +using Org.BouncyCastle.Ocsp; +using System.Runtime.ConstrainedExecution; namespace Keyfactor.AnyGateway.GoDaddy.API { - class APIProcessor : LoggingClientBase - { - private string ApiUrl { get; set; } - private string ApiKey { get; set; } - private string ShopperId { get; set; } + internal class APIProcessor : LoggingClientBase + { + private string ApiUrl { get; set; } + private string ApiKey { get; set; } + private string ShopperId { get; set; } + private int Timeout { get; set; } + private int MaxNumberOfTimeouts { get; set; } + private const string NO_CERTS_PURCHASED_MESSAGE = "Failed to create certificate order"; - public APIProcessor(string apiUrl, string apiKey, string shopperId) - { - Logger.MethodEntry(ILogExtensions.MethodLogLevel.Debug); + private const string NO_CERTS_PURCHASED_REPL_MESSAGE = "Failed to create certificate order. This error often occurs if there are no certificates purchased to fulfill this enrollment request. " + + "Please check your GoDaddy account to make sure you have the correct SSL certificate product purchased to cover this enrollment."; - ApiUrl = apiUrl; - ApiKey = apiKey; - ShopperId = shopperId; - Logger.MethodExit(ILogExtensions.MethodLogLevel.Debug); - } + internal int TotalNumberOfTimeouts { get; set; } = 0; - public string EnrollCSR(string csr, POSTCertificateRequest requestBody) - { - Logger.MethodEntry(ILogExtensions.MethodLogLevel.Debug); - - string rtnMessage = string.Empty; + internal int TotalDurationOfDownloadApiCallsInMilliseconds { get; set; } = 0; - string RESOURCE = "v1/certificates"; - RestRequest request = new RestRequest(RESOURCE, Method.POST); - request.AddJsonBody(requestBody); + public APIProcessor(string apiUrl, string apiKey, string shopperId, int timeout, int maxNumberOfTimeouts) + { + Logger.MethodEntry(ILogExtensions.MethodLogLevel.Debug); - Logger.MethodExit(ILogExtensions.MethodLogLevel.Debug); + ApiUrl = apiUrl; + ApiKey = apiKey; + ShopperId = shopperId; + Timeout = timeout; + MaxNumberOfTimeouts = maxNumberOfTimeouts; + TotalNumberOfTimeouts = 0; - return SubmitRequest(request); - } + Logger.MethodExit(ILogExtensions.MethodLogLevel.Debug); + } - public string RenewReissueCSR(string certificateId, string csr, POSTCertificateRenewalRequest requestBody, bool isRenew) - { - Logger.MethodEntry(ILogExtensions.MethodLogLevel.Debug); + public string EnrollCSR(string csr, POSTCertificateRequest requestBody) + { + Logger.MethodEntry(ILogExtensions.MethodLogLevel.Debug); - string rtnMessage = string.Empty; - string endpoint = isRenew ? "renew" : "reissue"; + string rtnMessage = string.Empty; - string RESOURCE = $"v1/certificates/{certificateId}/{endpoint}"; - RestRequest request = new RestRequest(RESOURCE, Method.POST); + string RESOURCE = "v1/certificates"; + RestRequest request = new RestRequest(RESOURCE, Method.POST); - request.AddJsonBody(requestBody); + request.AddJsonBody(requestBody); - Logger.MethodExit(ILogExtensions.MethodLogLevel.Debug); + Logger.MethodExit(ILogExtensions.MethodLogLevel.Debug); - return SubmitRequest(request); - } + Logger.Trace($"Json Request Body: {JsonConvert.SerializeObject(requestBody)}"); - public string GetCertificates(string customerId, int pageNumber, int pageSize) - { - Logger.MethodEntry(ILogExtensions.MethodLogLevel.Debug); + return SubmitRequest(request); + } - string rtnMessage = string.Empty; + public string RenewReissueCSR(string certificateId, string csr, POSTCertificateRenewalRequest requestBody, bool isRenew) + { + Logger.MethodEntry(ILogExtensions.MethodLogLevel.Debug); - string RESOURCE = $"v2/customers/{customerId}/certificates?offset={pageNumber.ToString()}&limit={pageSize.ToString()}"; - RestRequest request = new RestRequest(RESOURCE, Method.GET); + string rtnMessage = string.Empty; + string endpoint = isRenew ? "renew" : "reissue"; - Logger.MethodExit(ILogExtensions.MethodLogLevel.Debug); + string RESOURCE = $"v1/certificates/{certificateId}/{endpoint}"; + RestRequest request = new RestRequest(RESOURCE, Method.POST); - return SubmitRequest(request); - } + request.AddJsonBody(requestBody); - public string GetCertificate(string certificateId) - { - Logger.MethodEntry(ILogExtensions.MethodLogLevel.Debug); + Logger.MethodExit(ILogExtensions.MethodLogLevel.Debug); - string rtnMessage = string.Empty; + Logger.Trace($"Json Request Body: {JsonConvert.SerializeObject(requestBody)}"); - string RESOURCE = $"v1/certificates/{certificateId}"; - RestRequest request = new RestRequest(RESOURCE, Method.GET); + return SubmitRequest(request); + } - Logger.MethodExit(ILogExtensions.MethodLogLevel.Debug); + public string GetCertificates(string customerId, int pageNumber, int pageSize, int maxRetries) + { + Logger.MethodEntry(ILogExtensions.MethodLogLevel.Debug); - return SubmitRequest(request); - } + string rtnMessage = string.Empty; - public string DownloadCertificate(string certificateId) - { - Logger.MethodEntry(ILogExtensions.MethodLogLevel.Debug); + string RESOURCE = $"v2/customers/{customerId}/certificates?offset={pageNumber.ToString()}&limit={pageSize.ToString()}"; + RestRequest request = new RestRequest(RESOURCE, Method.GET); - string rtnMessage = string.Empty; + Logger.MethodExit(ILogExtensions.MethodLogLevel.Debug); - string RESOURCE = $"v1/certificates/{certificateId}/download"; - RestRequest request = new RestRequest(RESOURCE, Method.GET); - - Logger.MethodExit(ILogExtensions.MethodLogLevel.Debug); - - return SubmitRequest(request); - } - - public void RevokeCertificate(string certificateId, POSTCertificateRevokeRequest.REASON reason) - { - Logger.MethodEntry(ILogExtensions.MethodLogLevel.Debug); - - string rtnMessage = string.Empty; - - string RESOURCE = $"v1/certificates/{certificateId}/revoke"; - RestRequest request = new RestRequest(RESOURCE, Method.POST); + int retries = 0; + while (true) + { + try + { + rtnMessage = SubmitRequest(request); + break; + } + catch (GoDaddyTimeoutException ex) + { + retries++; + if (retries > maxRetries) + { + string msg = $"Maximum number of timeout retries of {maxRetries} exceeded for certificate page retrieval."; + Logger.Error(msg); + throw new GoDaddyMaxTimeoutException(msg); + } + else + continue; + } + } - POSTCertificateRevokeRequest body = new POSTCertificateRevokeRequest(); - body.reason = reason.ToString(); + return rtnMessage; + } - request.AddJsonBody(body); - SubmitRequest(request); + public string GetCertificate(string certificateId) + { + Logger.MethodEntry(ILogExtensions.MethodLogLevel.Debug); - Logger.MethodExit(ILogExtensions.MethodLogLevel.Debug); - } + string rtnMessage = string.Empty; - public string GetCustomerId() - { - Logger.MethodEntry(ILogExtensions.MethodLogLevel.Debug); + string RESOURCE = $"v1/certificates/{certificateId}"; + RestRequest request = new RestRequest(RESOURCE, Method.GET); - string rtnMessage = string.Empty; + Logger.MethodExit(ILogExtensions.MethodLogLevel.Debug); - string RESOURCE = $"v1/shoppers/{ShopperId}?includes=customerId"; - RestRequest request = new RestRequest(RESOURCE, Method.GET); + return SubmitRequest(request); + } - Logger.MethodExit(ILogExtensions.MethodLogLevel.Debug); + public string DownloadCertificate(string certificateId, int maxRetries) + { + Logger.MethodEntry(ILogExtensions.MethodLogLevel.Debug); - return SubmitRequest(request); - } + string cert = string.Empty; - public static int MapReturnStatus(CertificateStatusEnum status) - { - PKIConstants.Microsoft.RequestDisposition returnStatus = PKIConstants.Microsoft.RequestDisposition.UNKNOWN; + string RESOURCE = $"v1/certificates/{certificateId}/download"; + RestRequest request = new RestRequest(RESOURCE, Method.GET); - switch (status) - { - case CertificateStatusEnum.DENIED: - returnStatus = PKIConstants.Microsoft.RequestDisposition.DENIED; - break; - case CertificateStatusEnum.EXPIRED: - case CertificateStatusEnum.CURRENT: - case CertificateStatusEnum.ISSUED: - returnStatus = PKIConstants.Microsoft.RequestDisposition.ISSUED; - break; - case CertificateStatusEnum.PENDING_ISSUANCE: - returnStatus = PKIConstants.Microsoft.RequestDisposition.EXTERNAL_VALIDATION; - break; - case CertificateStatusEnum.REVOKED: - returnStatus = PKIConstants.Microsoft.RequestDisposition.REVOKED; - break; - default: - returnStatus = PKIConstants.Microsoft.RequestDisposition.FAILED; - break; - } + Logger.MethodExit(ILogExtensions.MethodLogLevel.Debug); - return Convert.ToInt32(returnStatus); - } + int retries = 0; + while (true) + { + try + { + DateTime before = DateTime.Now; + cert = SubmitRequest(request); + DateTime after = DateTime.Now; + TotalDurationOfDownloadApiCallsInMilliseconds += after.Subtract(before).Milliseconds; - public static POSTCertificateRevokeRequest.REASON MapRevokeReason(uint reason) - { - POSTCertificateRevokeRequest.REASON returnReason = POSTCertificateRevokeRequest.REASON.PRIVILEGE_WITHDRAWN; + break; + } + catch (GoDaddyTimeoutException ex) + { + retries++; + if (retries > maxRetries) + { + Logger.Warn($"Maximum number of timeout retries of {maxRetries} exceeded for certificate {certificateId} retrieval. Certificate skipped."); + throw ex; + } + else + continue; + } + } - switch (reason) - { - case 1: - returnReason = POSTCertificateRevokeRequest.REASON.KEY_COMPROMISE; - break; - case 3: - returnReason = POSTCertificateRevokeRequest.REASON.AFFILIATION_CHANGED; - break; - case 4: - returnReason = POSTCertificateRevokeRequest.REASON.SUPERSEDED; - break; - case 5: - returnReason = POSTCertificateRevokeRequest.REASON.CESSATION_OF_OPERATION; - break; - } + return cert; + } - return returnReason; - } + public void RevokeCertificate(string certificateId, POSTCertificateRevokeRequest.REASON reason) + { + Logger.MethodEntry(ILogExtensions.MethodLogLevel.Debug); + string rtnMessage = string.Empty; - #region Private Methods - private string SubmitRequest(RestRequest request) - { - Logger.MethodEntry(ILogExtensions.MethodLogLevel.Debug); - Logger.Trace($"Request Resource: {request.Resource}"); - Logger.Trace($"Request Method: {request.Method.ToString()}"); - Logger.Trace($"Request Body: {(request.Body == null ? string.Empty : request.Body.Value.ToString())}"); + string RESOURCE = $"v1/certificates/{certificateId}/revoke"; + RestRequest request = new RestRequest(RESOURCE, Method.POST); - IRestResponse response; + POSTCertificateRevokeRequest body = new POSTCertificateRevokeRequest(); + body.reason = reason.ToString(); - RestClient client = new RestClient(ApiUrl); - request.AddHeader("Authorization", ApiKey); + request.AddJsonBody(body); - try - { - response = client.Execute(request); - } - catch (Exception ex) - { - string exceptionMessage = GoDaddyException.FlattenExceptionMessages(ex, $"Error processing {request.Resource}"); - Logger.Error(exceptionMessage); - throw new GoDaddyException(exceptionMessage); - } + Logger.Trace($"Json Request Body: {JsonConvert.SerializeObject(body)}"); + SubmitRequest(request); - if (response.StatusCode != System.Net.HttpStatusCode.OK && - response.StatusCode != System.Net.HttpStatusCode.Accepted && - response.StatusCode != System.Net.HttpStatusCode.Created && - response.StatusCode != System.Net.HttpStatusCode.NoContent) - { - string errorMessage; + Logger.MethodExit(ILogExtensions.MethodLogLevel.Debug); + } - try - { - APIError error = JsonConvert.DeserializeObject(response.Content); - errorMessage = $"{error.code}: {error.message}"; - } - catch (JsonReaderException ex) - { - errorMessage = response.Content; - } + public string GetCustomerId() + { + Logger.MethodEntry(ILogExtensions.MethodLogLevel.Debug); - string exceptionMessage = $"Error processing {request.Resource}: {errorMessage}"; - Logger.Error(exceptionMessage); - throw new GoDaddyException(exceptionMessage); - } + string rtnMessage = string.Empty; - Logger.Trace($"API Result: {response.Content}"); - Logger.MethodExit(ILogExtensions.MethodLogLevel.Debug); + string RESOURCE = $"v1/shoppers/{ShopperId}?includes=customerId"; + RestRequest request = new RestRequest(RESOURCE, Method.GET); - return response.Content; - } - #endregion - } -} + Logger.MethodExit(ILogExtensions.MethodLogLevel.Debug); + + return SubmitRequest(request); + } + + public static int MapReturnStatus(CertificateStatusEnum status) + { + PKIConstants.Microsoft.RequestDisposition returnStatus = PKIConstants.Microsoft.RequestDisposition.UNKNOWN; + + switch (status) + { + case CertificateStatusEnum.DENIED: + returnStatus = PKIConstants.Microsoft.RequestDisposition.DENIED; + break; + + case CertificateStatusEnum.EXPIRED: + case CertificateStatusEnum.CURRENT: + case CertificateStatusEnum.ISSUED: + returnStatus = PKIConstants.Microsoft.RequestDisposition.ISSUED; + break; + + case CertificateStatusEnum.PENDING_ISSUANCE: + returnStatus = PKIConstants.Microsoft.RequestDisposition.EXTERNAL_VALIDATION; + break; + + case CertificateStatusEnum.REVOKED: + returnStatus = PKIConstants.Microsoft.RequestDisposition.REVOKED; + break; + + default: + returnStatus = PKIConstants.Microsoft.RequestDisposition.FAILED; + break; + } + + return Convert.ToInt32(returnStatus); + } + + public static POSTCertificateRevokeRequest.REASON MapRevokeReason(uint reason) + { + POSTCertificateRevokeRequest.REASON returnReason = POSTCertificateRevokeRequest.REASON.PRIVILEGE_WITHDRAWN; + + switch (reason) + { + case 1: + returnReason = POSTCertificateRevokeRequest.REASON.KEY_COMPROMISE; + break; + + case 3: + returnReason = POSTCertificateRevokeRequest.REASON.AFFILIATION_CHANGED; + break; + + case 4: + returnReason = POSTCertificateRevokeRequest.REASON.SUPERSEDED; + break; + + case 5: + returnReason = POSTCertificateRevokeRequest.REASON.CESSATION_OF_OPERATION; + break; + } + + return returnReason; + } + + #region Private Methods + + private string SubmitRequest(RestRequest request) + { + Logger.MethodEntry(ILogExtensions.MethodLogLevel.Debug); + Logger.Trace($"Request Resource: {request.Resource}"); + foreach (Parameter parameter in request.Parameters) + { + if (parameter.Name.ToLower() != "authorization") + Logger.Trace($"{parameter.Name}: {parameter.Value.ToString()}"); + } + Logger.Trace($"Request Method: {request.Method.ToString()}"); + + IRestResponse response = null; + + RestClient client = new RestClient(ApiUrl); + client.Timeout = Timeout; + + if (!request.Parameters.Exists(p => p.Name == "Authorization")) + request.AddHeader("Authorization", ApiKey); + + try + { + response = client.Execute(request); + Logger.Trace($"Http Status Code: {response.StatusCode}"); + Logger.Trace($"Response Status: {response.ResponseStatus}"); + + if (response.ResponseStatus == ResponseStatus.TimedOut || response.StatusCode == 0) + { + string msg = "Request timed out. "; + TotalNumberOfTimeouts++; + + if (TotalNumberOfTimeouts >= MaxNumberOfTimeouts) + { + msg += $"Maximum timeouts of {MaxNumberOfTimeouts} exceeded. "; + throw new GoDaddyMaxTimeoutException(msg); + } + else + { + Logger.Debug(msg); + throw new GoDaddyTimeoutException(msg); + } + } + } + catch (GoDaddyTimeoutException ex) { throw ex; } + catch (Exception ex) + { + string exceptionMessage = GoDaddyException.FlattenExceptionMessages(ex, $"Error processing {request.Resource}").Replace(NO_CERTS_PURCHASED_MESSAGE, NO_CERTS_PURCHASED_REPL_MESSAGE); + Logger.Error(exceptionMessage); + throw ex; + } + + if (response.StatusCode != System.Net.HttpStatusCode.OK && + response.StatusCode != System.Net.HttpStatusCode.Accepted && + response.StatusCode != System.Net.HttpStatusCode.Created && + response.StatusCode != System.Net.HttpStatusCode.NoContent) + { + string errorMessage; + + try + { + APIError error = JsonConvert.DeserializeObject(response.Content); + if (error == null) + errorMessage = "No error message returned."; + else + errorMessage = $"{error.code}: {error.message}"; + } + catch (JsonReaderException) + { + errorMessage = response.Content; + } + + string exceptionMessage = $"Error processing {request.Resource}: {errorMessage.Replace(NO_CERTS_PURCHASED_MESSAGE, NO_CERTS_PURCHASED_REPL_MESSAGE)}"; + Logger.Error(exceptionMessage); + throw new GoDaddyException(exceptionMessage); + } + + Logger.Trace($"API Result: {response.Content}"); + Logger.MethodExit(ILogExtensions.MethodLogLevel.Debug); + + return response.Content; + } + + #endregion Private Methods + } +} \ No newline at end of file diff --git a/GoDaddy/API/GoDaddyMaxTimeoutException.cs b/GoDaddy/API/GoDaddyMaxTimeoutException.cs new file mode 100644 index 0000000..2b1add1 --- /dev/null +++ b/GoDaddy/API/GoDaddyMaxTimeoutException.cs @@ -0,0 +1,27 @@ +// Copyright 2021 Keyfactor +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +using System; + +namespace Keyfactor.AnyGateway.GoDaddy.API +{ + class GoDaddyMaxTimeoutException : GoDaddyException + { + public GoDaddyMaxTimeoutException(string message) : base(message) + { } + + public GoDaddyMaxTimeoutException(string message, Exception ex) : base(message, ex) + { } + } +} diff --git a/GoDaddy/API/GoDaddyTimeoutException.cs b/GoDaddy/API/GoDaddyTimeoutException.cs new file mode 100644 index 0000000..b656275 --- /dev/null +++ b/GoDaddy/API/GoDaddyTimeoutException.cs @@ -0,0 +1,27 @@ +// Copyright 2021 Keyfactor +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +using System; + +namespace Keyfactor.AnyGateway.GoDaddy.API +{ + class GoDaddyTimeoutException : GoDaddyException + { + public GoDaddyTimeoutException(string message) : base(message) + { } + + public GoDaddyTimeoutException(string message, Exception ex) : base(message, ex) + { } + } +} diff --git a/GoDaddy/GoDaddy.csproj b/GoDaddy/GoDaddy.csproj index 4b54af6..8c849e6 100644 --- a/GoDaddy/GoDaddy.csproj +++ b/GoDaddy/GoDaddy.csproj @@ -145,6 +145,8 @@ + + diff --git a/GoDaddy/GoDaddyCAProxy.cs b/GoDaddy/GoDaddyCAProxy.cs index 7e7912b..a7ed9dd 100644 --- a/GoDaddy/GoDaddyCAProxy.cs +++ b/GoDaddy/GoDaddyCAProxy.cs @@ -1,16 +1,593 @@ -// Copyright 2021 Keyfactor // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. using CAProxy.AnyGateway.Interfaces; using CAProxy.AnyGateway.Models; using CAProxy.Common; using CSS.Common; using CSS.Common.Logging; using Keyfactor.PKI; using Keyfactor.PKI.PEM; using System; using System.Collections.Concurrent; using System.Collections.Generic; using System.Linq; using System.Threading; using System.Threading.Tasks; using Newtonsoft.Json; using Keyfactor.AnyGateway.GoDaddy.API; using Keyfactor.AnyGateway.GoDaddy.Models; namespace Keyfactor.AnyGateway.GoDaddy { public class GoDaddyCAProxy : CAProxy.AnyGateway.BaseCAConnector { private APIProcessor _api { get; set; } private string _rootType { get; set; } private int _syncPageSize { get; set; } private int _enrollmentRetries { get; set; } private int _secondsBetweenEnrollmentRetries { get; set; } private string[][] _connectionKeys = new string[][] { new string[] { "ApiUrl", "string" }, new string[] { "ApiKey", "string" }, new string[] { "ShopperId", "string" }, new string[] { "RootType", "string" }, new string[] { "SyncPageSize", "int" }, new string[] { "EnrollmentRetries", "int" }, new string[] { "SecondsBetweenEnrollmentRetries", "int" } }; #region Interface Methods public override void Initialize(ICAConnectorConfigProvider configProvider) { Logger.MethodEntry(ILogExtensions.MethodLogLevel.Debug); foreach (KeyValuePair configEntry in configProvider.CAConnectionData) Logger.Trace($"{configEntry.Key}: {configEntry.Value}"); ValidateParameters(configProvider.CAConnectionData, _connectionKeys); string apiUrl = configProvider.CAConnectionData["ApiUrl"].ToString(); string apiKey = configProvider.CAConnectionData["ApiKey"].ToString(); string shopperId = configProvider.CAConnectionData["ShopperId"].ToString(); _rootType = configProvider.CAConnectionData["RootType"].ToString(); _syncPageSize = Convert.ToInt32(configProvider.CAConnectionData["SyncPageSize"]); _enrollmentRetries = Convert.ToInt32(configProvider.CAConnectionData["EnrollmentRetries"]); _secondsBetweenEnrollmentRetries = Convert.ToInt32(configProvider.CAConnectionData["SecondsBetweenEnrollmentRetries"]); _api = new APIProcessor(apiUrl, apiKey, shopperId); Logger.MethodExit(ILogExtensions.MethodLogLevel.Debug); } public override void Ping() { Logger.MethodEntry(ILogExtensions.MethodLogLevel.Debug); Logger.MethodExit(ILogExtensions.MethodLogLevel.Debug); } public override void ValidateCAConnectionInfo(Dictionary connectionInfo) { Logger.MethodEntry(ILogExtensions.MethodLogLevel.Debug); ValidateParameters(connectionInfo, _connectionKeys); Logger.MethodExit(ILogExtensions.MethodLogLevel.Debug); } [Obsolete] public override void Synchronize(ICertificateDataReader certificateDataReader, BlockingCollection blockingBuffer, CertificateAuthoritySyncInfo certificateAuthoritySyncInfo, CancellationToken cancelToken, string logicalName) { Logger.MethodEntry(ILogExtensions.MethodLogLevel.Debug); Logger.MethodExit(ILogExtensions.MethodLogLevel.Debug); throw new NotImplementedException(); } public override void Synchronize(ICertificateDataReader certificateDataReader, BlockingCollection blockingBuffer, CertificateAuthoritySyncInfo certificateAuthoritySyncInfo, CancellationToken cancelToken) { Logger.MethodEntry(ILogExtensions.MethodLogLevel.Debug); string customerId = JsonConvert.DeserializeObject(_api.GetCustomerId()).customerId; int pageNumber = 1; bool wasLastPage = false; do { GETCertificatesDetailsResponse certificates = JsonConvert.DeserializeObject(_api.GetCertificates(customerId, pageNumber, _syncPageSize)); foreach (CertificateDetails certificate in certificates.certificates) { Thread.Sleep(1000); try { string issuedCert = RemovePEMHeader(JsonConvert.DeserializeObject(_api.DownloadCertificate(certificate.certificateId)).pems.certificate); CertificateStatusEnum certStatus = CertificateStatusEnum.ISSUED; if (!Enum.TryParse(certificate.status, out certStatus)) certStatus = CertificateStatusEnum.CANCELED; blockingBuffer.Add(new CAConnectorCertificate { CARequestID = certificate.certificateId, Certificate = issuedCert, CSR = string.Empty, ResolutionDate = certificate.completedAt, RevocationDate = certificate.revokedAt, RevocationReason = null, Status = APIProcessor.MapReturnStatus(certStatus), SubmissionDate = certificate.createdAt, ProductID = certificate.type }); } catch (GoDaddyException) { } } wasLastPage = certificates.pagination.previous == certificates.pagination.last; pageNumber++; } while (!wasLastPage); blockingBuffer.CompleteAdding(); Logger.MethodExit(ILogExtensions.MethodLogLevel.Debug); } [Obsolete] public override EnrollmentResult Enroll(string csr, string subject, Dictionary san, EnrollmentProductInfo productInfo, CSS.PKI.PKIConstants.X509.RequestFormat requestFormat, RequestUtilities.EnrollmentType enrollmentType) { Logger.MethodEntry(ILogExtensions.MethodLogLevel.Debug); Logger.MethodExit(ILogExtensions.MethodLogLevel.Debug); throw new NotImplementedException(); } public override EnrollmentResult Enroll(ICertificateDataReader certificateDataReader, string csr, string subject, Dictionary san, EnrollmentProductInfo productInfo, CSS.PKI.PKIConstants.X509.RequestFormat requestFormat, RequestUtilities.EnrollmentType enrollmentType) { Logger.MethodEntry(ILogExtensions.MethodLogLevel.Debug); foreach (KeyValuePair configEntry in productInfo.ProductParameters) Logger.Trace($"{configEntry.Key}: {configEntry.Value}"); +// Copyright 2021 Keyfactor +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. - string[][] parameterKeys = new string[][] { }; if (enrollmentType == RequestUtilities.EnrollmentType.New) - parameterKeys = new string[][] { new string[] { "Email", "string" }, new string[] { "FirstName", "string" }, new string[] { "LastName", "string" }, new string[] { "Phone", "string" }, new string[] { "CertificatePeriodInYears", "int" } }; else - parameterKeys = new string[][] { new string[] { "PriorCertSN", "string" } }; +using CAProxy.AnyGateway.Interfaces; using CAProxy.AnyGateway.Models; using CAProxy.Common; using CSS.Common; using CSS.Common.Logging; using Keyfactor.PKI; using Keyfactor.PKI.PEM; using System; using System.Collections.Concurrent; using System.Collections.Generic; using System.Linq; using System.Threading; using System.Threading.Tasks; using Newtonsoft.Json; using Keyfactor.AnyGateway.GoDaddy.API; using Keyfactor.AnyGateway.GoDaddy.Models; namespace Keyfactor.AnyGateway.GoDaddy { + public class GoDaddyCAProxy : CAProxy.AnyGateway.BaseCAConnector + { + private APIProcessor _api; + private string _rootType; - ValidateParameters(productInfo.ProductParameters, parameterKeys); - POSTCertificateEnrollmentResponse enrollmentResponse = new POSTCertificateEnrollmentResponse(); try { - switch (enrollmentType) { case RequestUtilities.EnrollmentType.New: - switch (productInfo.ProductID) { case "DV_SSL": case "DV_WILDCARD_SSL": case "UCC_DV_SSL": enrollmentResponse = EnrollDV(productInfo, csr, san); break; case "OV_SSL": case "OV_CS": case "OV_DS": case "OV_WILDCARD_SSL": case "UCC_OV_SSL": enrollmentResponse = EnrollOV(productInfo, csr, san); break; case "EV_SSL": case "UCC_EV_SSL": enrollmentResponse = EnrollEV(productInfo, csr, san); break; default: return new EnrollmentResult { Status = 30, StatusMessage = $"Error attempting to enroll certificate {subject}: Invalid Product ID - {productInfo.ProductID}." }; } break; case RequestUtilities.EnrollmentType.Renew: case RequestUtilities.EnrollmentType.Reissue: - CAConnectorCertificate certificate = certificateDataReader.GetCertificateRecord(DataConversion.HexToBytes(productInfo.ProductParameters["PriorCertSN"])); enrollmentResponse = RenewReissue(certificate.CARequestID, productInfo, csr, san, enrollmentType == RequestUtilities.EnrollmentType.Renew); break; default: return new EnrollmentResult { Status = 30, StatusMessage = $"Unsupported EnrollmentType: {enrollmentType}" }; } } catch (Exception ex) { return new EnrollmentResult { Status = 30, StatusMessage = $"Error attempting to enroll certificate {subject}: {ex.Message}." }; } EnrollmentResult result = new EnrollmentResult(); CertificateStatusEnum certStatus = CertificateStatusEnum.PENDING_ISSUANCE; for(int i = 0; i < _enrollmentRetries; i++) { try { GETCertificateDetailsResponse certResponse = JsonConvert.DeserializeObject(_api.GetCertificate(enrollmentResponse.certificateId)); Enum.TryParse(certResponse.status, out certStatus); if (certStatus == CertificateStatusEnum.ISSUED) break; } catch (Exception) { } Thread.Sleep(_secondsBetweenEnrollmentRetries * 1000); } string pemCertificate = certStatus == CertificateStatusEnum.ISSUED ? RemovePEMHeader(JsonConvert.DeserializeObject(_api.DownloadCertificate(enrollmentResponse.certificateId)).pems.certificate) : string.Empty; Logger.MethodExit(ILogExtensions.MethodLogLevel.Debug); return new EnrollmentResult { CARequestID = enrollmentResponse.certificateId, Certificate = pemCertificate, Status = APIProcessor.MapReturnStatus(certStatus), StatusMessage = $"GoDaddy Status = {certStatus.ToString()}" }; } public override CAConnectorCertificate GetSingleRecord(string caRequestID) { Logger.MethodEntry(ILogExtensions.MethodLogLevel.Debug); CertificateStatusEnum certStatus = CertificateStatusEnum.PENDING_ISSUANCE; GETCertificateDetailsResponse certResponse = JsonConvert.DeserializeObject(_api.GetCertificate(caRequestID)); Enum.TryParse(certResponse.status, out certStatus); string issuedCert = string.Empty; if (certStatus == CertificateStatusEnum.ISSUED || certStatus == CertificateStatusEnum.REVOKED || certStatus == CertificateStatusEnum.EXPIRED) { issuedCert = RemovePEMHeader(JsonConvert.DeserializeObject(_api.DownloadCertificate(caRequestID)).pems.certificate); + private int _syncPageSize = 50; + private const int SYNC_PAGE_SIZE_MIN = 10; + private const int SYNC_PAGE_SIZE_MAX = 1000; + + private int _enrollmentRetries = 2; + private const int ENROLLMENT_RETRIES_MIN = 0; + private const int ENROLLMENT_RETRIES_MAX = 5; + + private int _secondsBetweenEnrollmentRetries = 5; + private const int SECONDS_BETWEEN_ENROLLMENT_RETRIES_MIN = 2; + private const int SECONDS_BETWEEN_ENROLLMENT_RETRIES_MAX = 20; + + private int _apiTimeoutInSeconds = 20; + private const int API_TIMEOUT_IN_SECONDS_MIN = 2; + private const int API_TIMEOUT_IN_SECONDS_MAX = 100; + + private int _numberOfCertPageRetrievalRetriesBeforeFailure = 2; + private const int NUMBER_OF_CERT_PAGE_RETRIEVAL_RETRIES_BEFORE_FAILURE_MIN = 0; + private const int NUMBER_OF_CERT_PAGE_RETRIEVAL_RETRIES_BEFORE_FAILURE_MAX = 10; + + private int _numberOfCertDownloadRetriesBeforeSkip = 2; + private const int NUMBER_OF_CERT_DOWNLOAD_RETRIES_BEFORE_SKIP_MIN = 0; + private const int NUMBER_OF_CERT_DOWNLOAD_RETRIES_BEFORE_SKIP_MAX = 10; + + private int _numberOfTimeoutsBeforeSyncFailure = 100; + private const int NUMBER_OF_TIMEOUTS_BEFORE_SYNC_FAILURE_MIN = 0; + private const int NUMBER_OF_TIMEOUTS_BEFORE_SYNC_FAILURE_MAX = 5000; + + private int _millisecondsBetweenCertDownloads = 1000; + private const int MILLISECONDS_BETWEEN_CERT_DOWNLOADS_MIN = 0; + private const int MILLISECONDS_BETWEEN_CERT_DOWNLOADS_MAX = 1000; + + + private string[][] _connectionKeys = new string[][] { new string[] { "ApiUrl", "string" }, + new string[] { "ApiKey", "string" }, + new string[] { "ShopperId", "string" }, + new string[] { "RootType", "string" }, + new string[] { "SyncPageSize", "int" } }; + + + #region Interface Methods + public override void Initialize(ICAConnectorConfigProvider configProvider) + { + Logger.MethodEntry(ILogExtensions.MethodLogLevel.Debug); + + Logger.Info($"Keyfactor Gateway Version: {System.Reflection.Assembly.GetCallingAssembly().GetName().Version.ToString()}"); + List assemblies = System.AppDomain.CurrentDomain.GetAssemblies().Where(p => p.FullName.ToLower().Contains("godaddy")).ToList(); + if (assemblies.Count == 1) + Logger.Info($"GoDaddy Gateway Version: {assemblies[0].FullName}"); + + Logger.Trace("GATEWAY CONFIG SETTINGS:"); + foreach (KeyValuePair configEntry in configProvider.CAConnectionData) + { + if (configEntry.Key.ToLower() != "apikey") + Logger.Trace($" {configEntry.Key}: {configEntry.Value}"); + } + ValidateParameters(configProvider.CAConnectionData, _connectionKeys); + + string apiUrl = configProvider.CAConnectionData["ApiUrl"].ToString(); + string apiKey = configProvider.CAConnectionData["ApiKey"].ToString(); + string shopperId = configProvider.CAConnectionData["ShopperId"].ToString(); + _rootType = configProvider.CAConnectionData["RootType"].ToString(); + + //optional parameters + bool isInt; + int tempInt; + + if (configProvider.CAConnectionData.ContainsKey("SyncPageSize")) + { + isInt = int.TryParse(configProvider.CAConnectionData["SyncPageSize"].ToString(), out tempInt); + _syncPageSize = !isInt || tempInt < SYNC_PAGE_SIZE_MIN || tempInt > SYNC_PAGE_SIZE_MAX ? _syncPageSize : tempInt; + } + + if (configProvider.CAConnectionData.ContainsKey("EnrollmentRetries")) + { + isInt = int.TryParse(configProvider.CAConnectionData["EnrollmentRetries"].ToString(), out tempInt); + _enrollmentRetries = !isInt || tempInt < ENROLLMENT_RETRIES_MIN || tempInt > ENROLLMENT_RETRIES_MAX ? _enrollmentRetries : tempInt; + } + + if (configProvider.CAConnectionData.ContainsKey("SecondsBetweenEnrollmentRetries")) + { + isInt = int.TryParse(configProvider.CAConnectionData["SecondsBetweenEnrollmentRetries"].ToString(), out tempInt); + _secondsBetweenEnrollmentRetries = !isInt || tempInt < SECONDS_BETWEEN_ENROLLMENT_RETRIES_MIN || tempInt > SECONDS_BETWEEN_ENROLLMENT_RETRIES_MAX ? _secondsBetweenEnrollmentRetries : tempInt; + } + + if (configProvider.CAConnectionData.ContainsKey("ApiTimeoutinSeconds")) + { + isInt = int.TryParse(configProvider.CAConnectionData["ApiTimeoutinSeconds"].ToString(), out tempInt); + _apiTimeoutInSeconds = !isInt || tempInt < API_TIMEOUT_IN_SECONDS_MIN || tempInt > API_TIMEOUT_IN_SECONDS_MAX ? _apiTimeoutInSeconds : tempInt; + } + + if (configProvider.CAConnectionData.ContainsKey("NumberOfCertPageRetrievalRetriesBeforeFailure")) + { + isInt = int.TryParse(configProvider.CAConnectionData["NumberOfCertPageRetrievalRetriesBeforeFailure"].ToString(), out tempInt); + _numberOfCertPageRetrievalRetriesBeforeFailure = !isInt || tempInt < NUMBER_OF_CERT_PAGE_RETRIEVAL_RETRIES_BEFORE_FAILURE_MIN || tempInt > NUMBER_OF_CERT_PAGE_RETRIEVAL_RETRIES_BEFORE_FAILURE_MAX ? _numberOfCertPageRetrievalRetriesBeforeFailure : tempInt; } + if (configProvider.CAConnectionData.ContainsKey("NumberOfCertDownloadRetriesBeforeSkip")) + { + isInt = int.TryParse(configProvider.CAConnectionData["NumberOfCertDownloadRetriesBeforeSkip"].ToString(), out tempInt); + _numberOfCertDownloadRetriesBeforeSkip = !isInt || tempInt < NUMBER_OF_CERT_DOWNLOAD_RETRIES_BEFORE_SKIP_MIN || tempInt > NUMBER_OF_CERT_DOWNLOAD_RETRIES_BEFORE_SKIP_MAX ? _numberOfCertDownloadRetriesBeforeSkip : tempInt; + } + + if (configProvider.CAConnectionData.ContainsKey("NumberOfTimeoutsBeforeSyncFailure")) + { + isInt = int.TryParse(configProvider.CAConnectionData["NumberOfTimeoutsBeforeSyncFailure"].ToString(), out tempInt); + _numberOfTimeoutsBeforeSyncFailure = !isInt || tempInt < NUMBER_OF_TIMEOUTS_BEFORE_SYNC_FAILURE_MIN || tempInt > NUMBER_OF_TIMEOUTS_BEFORE_SYNC_FAILURE_MAX ? _numberOfTimeoutsBeforeSyncFailure : tempInt; + } + + if (configProvider.CAConnectionData.ContainsKey("MillisecondsBetweenCertDownloads")) + { + isInt = int.TryParse(configProvider.CAConnectionData["MillisecondsBetweenCertDownloads"].ToString(), out tempInt); + _millisecondsBetweenCertDownloads = !isInt || tempInt < MILLISECONDS_BETWEEN_CERT_DOWNLOADS_MIN || tempInt > MILLISECONDS_BETWEEN_CERT_DOWNLOADS_MAX ? _millisecondsBetweenCertDownloads : tempInt; + } + + _api = new APIProcessor(apiUrl, apiKey, shopperId, _apiTimeoutInSeconds * 1000, _numberOfTimeoutsBeforeSyncFailure); + + Logger.MethodExit(ILogExtensions.MethodLogLevel.Debug); + } + + public override void Ping() + { + Logger.MethodEntry(ILogExtensions.MethodLogLevel.Debug); + + Logger.MethodExit(ILogExtensions.MethodLogLevel.Debug); + } + + public override void ValidateCAConnectionInfo(Dictionary connectionInfo) + { + Logger.MethodEntry(ILogExtensions.MethodLogLevel.Debug); + + ValidateParameters(connectionInfo, _connectionKeys); + + Logger.MethodExit(ILogExtensions.MethodLogLevel.Debug); + } + + [Obsolete] + public override void Synchronize(ICertificateDataReader certificateDataReader, BlockingCollection blockingBuffer, CertificateAuthoritySyncInfo certificateAuthoritySyncInfo, CancellationToken cancelToken, string logicalName) + { + Logger.MethodEntry(ILogExtensions.MethodLogLevel.Debug); + + Logger.MethodExit(ILogExtensions.MethodLogLevel.Debug); + + throw new NotImplementedException(); + } + + public override void Synchronize(ICertificateDataReader certificateDataReader, BlockingCollection blockingBuffer, CertificateAuthoritySyncInfo certificateAuthoritySyncInfo, CancellationToken cancelToken) + { + Logger.MethodEntry(ILogExtensions.MethodLogLevel.Debug); + DateTime? overallLastSync = new DateTime(2020, 11, 11); + + string customerId = JsonConvert.DeserializeObject(_api.GetCustomerId()).customerId; + + int pageNumber = 1; + bool wasLastPage = false; + + int totalNumberOfCertsFound = 0; + int totalNumberOfCertsRetrieved = 0; + _api.TotalNumberOfTimeouts = 0; + _api.TotalDurationOfDownloadApiCallsInMilliseconds = 0; + do + { + Thread.Sleep(_millisecondsBetweenCertDownloads); + GETCertificatesDetailsResponse certificates = new GETCertificatesDetailsResponse(); + + try + { + certificates = JsonConvert.DeserializeObject(_api.GetCertificates(customerId, pageNumber, _syncPageSize, _numberOfCertPageRetrievalRetriesBeforeFailure)); + } + catch (GoDaddyMaxTimeoutException) + { + Logger.Error($"Sync failed due to maximum timeouts of {_numberOfCertPageRetrievalRetriesBeforeFailure.ToString()} being reached for certificate page retrieval."); + return; + } + catch (GoDaddyTimeoutException) { } + + if (!certificateAuthoritySyncInfo.DoFullSync && certificateAuthoritySyncInfo.OverallLastSync.HasValue) + certificates.certificates = certificates.certificates.Where(p => p.completedAt.HasValue && p.completedAt.Value > certificateAuthoritySyncInfo.OverallLastSync.Value.AddDays(-1)).ToList(); + + foreach (CertificateDetails certificate in certificates.certificates) + { + totalNumberOfCertsFound++; + Thread.Sleep(_millisecondsBetweenCertDownloads); + + try + { + string issuedCert = RemovePEMHeader(JsonConvert.DeserializeObject(_api.DownloadCertificate(certificate.certificateId, _numberOfCertDownloadRetriesBeforeSkip)).pems.certificate); + + CertificateStatusEnum certStatus = CertificateStatusEnum.ISSUED; + if (!Enum.TryParse(certificate.status, out certStatus)) + certStatus = CertificateStatusEnum.CANCELED; + + blockingBuffer.Add(new CAConnectorCertificate + { + CARequestID = certificate.certificateId, + Certificate = issuedCert, + CSR = string.Empty, + ResolutionDate = certificate.completedAt, + RevocationDate = certificate.revokedAt, + RevocationReason = null, + Status = APIProcessor.MapReturnStatus(certStatus), + SubmissionDate = certificate.createdAt, + ProductID = certificate.type + }); + + totalNumberOfCertsRetrieved++; + } + catch (GoDaddyMaxTimeoutException) + { + Logger.Error($"Sync failed due to maximum timeouts of {_numberOfTimeoutsBeforeSyncFailure.ToString()} being reached."); + return; + } + catch (GoDaddyTimeoutException) { } + catch (Exception) { } + } + + wasLastPage = certificates.pagination.previous == certificates.pagination.last; + pageNumber++; + } while (!wasLastPage); + + blockingBuffer.CompleteAdding(); + + string syncStats = "SYNC STATISTICS:" + System.Environment.NewLine; + syncStats += $" Total Certificates Found: {totalNumberOfCertsFound.ToString()}" + System.Environment.NewLine; + syncStats += $" Total Certificates Successfully Retrieved: {totalNumberOfCertsRetrieved.ToString()}" + System.Environment.NewLine; + syncStats += $" Total Number of GoDaddy Timeouts When Attempting to Retrieve Certificates: {_api.TotalNumberOfTimeouts.ToString()}" + System.Environment.NewLine; + + int avgDurationApiCallsInMilliseconds = totalNumberOfCertsRetrieved == 0 ? 0 : (_api.TotalDurationOfDownloadApiCallsInMilliseconds / totalNumberOfCertsRetrieved); + syncStats += $" Average Time in Milliseconds For Each Successful GoDaddy Certificate Retrieval API Call: {avgDurationApiCallsInMilliseconds.ToString()}" + System.Environment.NewLine; + + Logger.Info(syncStats); + Logger.MethodExit(ILogExtensions.MethodLogLevel.Debug); + } + + [Obsolete] + public override EnrollmentResult Enroll(string csr, string subject, Dictionary san, EnrollmentProductInfo productInfo, CSS.PKI.PKIConstants.X509.RequestFormat requestFormat, RequestUtilities.EnrollmentType enrollmentType) + { + Logger.MethodEntry(ILogExtensions.MethodLogLevel.Debug); + + Logger.MethodExit(ILogExtensions.MethodLogLevel.Debug); + + throw new NotImplementedException(); + } + + public override EnrollmentResult Enroll(ICertificateDataReader certificateDataReader, string csr, string subject, Dictionary san, EnrollmentProductInfo productInfo, CSS.PKI.PKIConstants.X509.RequestFormat requestFormat, RequestUtilities.EnrollmentType enrollmentType) + { + Logger.MethodEntry(ILogExtensions.MethodLogLevel.Debug); + + foreach (KeyValuePair configEntry in productInfo.ProductParameters) + { + Logger.Trace($"{configEntry.Key}: {configEntry.Value}"); + } + + string[][] parameterKeys; + if (enrollmentType == RequestUtilities.EnrollmentType.New) + parameterKeys = new string[][] { new string[] { "Email", "string" }, + new string[] { "FirstName", "string" }, + new string[] { "LastName", "string" }, + new string[] { "Phone", "string" }, + new string[] { "CertificatePeriodInYears", "int" } }; + else + parameterKeys = new string[][] { new string[] { "PriorCertSN", "string" } }; + + ValidateParameters(productInfo.ProductParameters, parameterKeys); + + POSTCertificateEnrollmentResponse enrollmentResponse; + + try + { + switch (enrollmentType) + { + case RequestUtilities.EnrollmentType.New: + switch (productInfo.ProductID) + { + case "DV_SSL": + case "DV_WILDCARD_SSL": + case "UCC_DV_SSL": + enrollmentResponse = EnrollDV(productInfo, csr, san); + break; + + case "OV_SSL": + case "OV_CS": + case "OV_DS": + case "OV_WILDCARD_SSL": + case "UCC_OV_SSL": + enrollmentResponse = EnrollOV(productInfo, csr, san); + break; + + case "EV_SSL": + case "UCC_EV_SSL": + enrollmentResponse = EnrollEV(productInfo, csr, san); + break; + + default: + return new EnrollmentResult { Status = 30, StatusMessage = $"Error attempting to enroll certificate {subject}: Invalid Product ID - {productInfo.ProductID}." }; + } + + break; + + case RequestUtilities.EnrollmentType.Renew: + case RequestUtilities.EnrollmentType.Reissue: + CAConnectorCertificate certificate = certificateDataReader.GetCertificateRecord(DataConversion.HexToBytes(productInfo.ProductParameters["PriorCertSN"])); + enrollmentResponse = RenewReissue(certificate.CARequestID, productInfo, csr, san, enrollmentType == RequestUtilities.EnrollmentType.Renew); + break; + + default: + return new EnrollmentResult { Status = 30, StatusMessage = $"Unsupported EnrollmentType: {enrollmentType}" }; + } + } + catch (Exception ex) + { + return new EnrollmentResult { Status = 30, StatusMessage = $"Error attempting to enroll certificate {subject}: {ex.Message}." }; + } Logger.Trace($"Enrollment issued for certificate ID {enrollmentResponse.certificateId}"); + + CertificateStatusEnum certStatus = CertificateStatusEnum.PENDING_ISSUANCE; + for (int i = 0; i < _enrollmentRetries; i++) + { + try + { + GETCertificateDetailsResponse certResponse = JsonConvert.DeserializeObject(_api.GetCertificate(enrollmentResponse.certificateId)); + Enum.TryParse(certResponse.status, out certStatus); + if (certStatus == CertificateStatusEnum.ISSUED) + break; + } + catch (Exception exc) { string errMsg = $"Error retrieving certificate fails for ID {enrollmentResponse.certificateId}:\n{LogHandler.FlattenException(exc)}"; if (i + 1 < _enrollmentRetries) + { + errMsg += $"\nRetrying... (Attempt {i + 1} of {_enrollmentRetries})"; + } else + { + errMsg += $"Retrieving certificate failed."; + } Logger.Error(errMsg); } + + Thread.Sleep(_secondsBetweenEnrollmentRetries * 1000); + } + + string pemCertificate = certStatus == CertificateStatusEnum.ISSUED + ? RemovePEMHeader(JsonConvert.DeserializeObject(_api.DownloadCertificate(enrollmentResponse.certificateId, _numberOfCertDownloadRetriesBeforeSkip)).pems.certificate) + : string.Empty; + + Logger.MethodExit(ILogExtensions.MethodLogLevel.Debug); + + return new EnrollmentResult + { + CARequestID = enrollmentResponse.certificateId, + Certificate = pemCertificate, + Status = APIProcessor.MapReturnStatus(certStatus), + StatusMessage = $"GoDaddy Status = {certStatus.ToString()}" + }; + } + + public override CAConnectorCertificate GetSingleRecord(string caRequestID) + { + Logger.MethodEntry(ILogExtensions.MethodLogLevel.Debug); + Logger.Trace($"Getting record for CARequestID: {caRequestID}"); + CertificateStatusEnum certStatus = CertificateStatusEnum.PENDING_ISSUANCE; + + GETCertificateDetailsResponse certResponse = JsonConvert.DeserializeObject(_api.GetCertificate(caRequestID)); + Enum.TryParse(certResponse.status, out certStatus); + + string issuedCert = string.Empty; + if (certStatus == CertificateStatusEnum.ISSUED || certStatus == CertificateStatusEnum.REVOKED || certStatus == CertificateStatusEnum.EXPIRED) + { + issuedCert = RemovePEMHeader(JsonConvert.DeserializeObject(_api.DownloadCertificate(caRequestID, _numberOfCertDownloadRetriesBeforeSkip)).pems.certificate); + } + + Logger.MethodExit(ILogExtensions.MethodLogLevel.Debug); + + return new CAConnectorCertificate() + { + CARequestID = caRequestID, + Certificate = issuedCert, + CSR = certResponse.csr, + ResolutionDate = certResponse.createdAt, + RevocationDate = certResponse.revokedAt, + RevocationReason = null, + Status = APIProcessor.MapReturnStatus(certStatus), + SubmissionDate = certResponse.createdAt + }; + } + + public override int Revoke(string caRequestID, string hexSerialNumber, uint revocationReason) + { + Logger.MethodEntry(ILogExtensions.MethodLogLevel.Debug); + + try + { + _api.RevokeCertificate(caRequestID, APIProcessor.MapRevokeReason(revocationReason)); + } + catch (Exception ex) + { + return Convert.ToInt32(PKIConstants.Microsoft.RequestDisposition.FAILED); + } + + Logger.MethodExit(ILogExtensions.MethodLogLevel.Debug); + + return Convert.ToInt32(PKIConstants.Microsoft.RequestDisposition.REVOKED); + } + + public override void ValidateProductInfo(EnrollmentProductInfo productInfo, Dictionary connectionInfo) + { + Logger.MethodEntry(ILogExtensions.MethodLogLevel.Debug); + + Logger.MethodExit(ILogExtensions.MethodLogLevel.Debug); + } + + + #endregion Interface Methods + + #region Private Methods + private string RemovePEMHeader(string pem) + { + return string.IsNullOrEmpty(pem) ? string.Empty : PemUtilities.DERToPEM(PemUtilities.PEMToDER(pem), PemUtilities.PemObjectType.NoHeaders); + } + + private void ValidateParameters(Dictionary connectionData, string[][] keysToValidate) + { + List errors = new List(); + + foreach (string[] connectionKey in keysToValidate) + { + if (!connectionData.ContainsKey(connectionKey[0])) + errors.Add($"CAConnection configuration value {connectionKey} not found."); + else if (connectionKey[1] == "int") + { + int value; + bool isIntValue = int.TryParse(connectionData[connectionKey[0]].ToString(), out value); + if (!isIntValue) + errors.Add($"CAConnection configuration value {connectionKey} must contain an integer. Found {connectionData[connectionKey[0]]}"); + } + } + + if (errors.Count > 0) + throw new GoDaddyException(string.Join(System.Environment.NewLine, errors.ToArray())); + } + + private POSTCertificateEnrollmentResponse EnrollDV(EnrollmentProductInfo productInfo, string csr, Dictionary san) + { + POSTCertificatesV1DVRequest certRequest = new POSTCertificatesV1DVRequest(); + + certRequest.contact = new ContactInfo(); + certRequest.contact.email = productInfo.ProductParameters["Email"]; + certRequest.contact.nameFirst = productInfo.ProductParameters["FirstName"]; + certRequest.contact.nameLast = productInfo.ProductParameters["LastName"]; + certRequest.contact.phone = productInfo.ProductParameters["Phone"]; + certRequest.SetCSR(csr); + + certRequest.period = Convert.ToInt32(productInfo.ProductParameters["CertificatePeriodInYears"]); + certRequest.productType = productInfo.ProductID; + certRequest.rootType = _rootType; + certRequest.slotSize = productInfo.ProductParameters.Keys.Contains("SlotSize") ? productInfo.ProductParameters["SlotSize"] : "FIVE"; + + List sans = new List(); + foreach (string[] sanValues in san.Values) + { + foreach (string sanValue in sanValues) + { + sans.Add(sanValue); + } + } + certRequest.subjectAlternativeNames = sans.ToArray(); + + string response = _api.EnrollCSR(csr, certRequest); + + return JsonConvert.DeserializeObject(response); + } + + private POSTCertificateEnrollmentResponse EnrollOV(EnrollmentProductInfo productInfo, string csr, Dictionary san) + { + POSTCertificatesV1OVRequest certRequest = new POSTCertificatesV1OVRequest(); + + certRequest.contact = new ContactInfo(); + certRequest.contact.jobTitle = productInfo.ProductParameters["JobTitle"]; + certRequest.contact.email = productInfo.ProductParameters["Email"]; + certRequest.contact.nameFirst = productInfo.ProductParameters["FirstName"]; + certRequest.contact.nameLast = productInfo.ProductParameters["LastName"]; + certRequest.contact.phone = productInfo.ProductParameters["Phone"]; + + certRequest.organization = new OrganizationInfo(); + certRequest.organization.address = new AddressInfo(); + certRequest.organization.address.address1 = productInfo.ProductParameters["OrganizationAddress"]; + certRequest.organization.address.city = productInfo.ProductParameters["OrganizationCity"]; + certRequest.organization.address.state = productInfo.ProductParameters["OrganizationState"]; + certRequest.organization.address.country = productInfo.ProductParameters["OrganizationCountry"]; + certRequest.organization.name = productInfo.ProductParameters["OrganizationName"]; + certRequest.organization.phone = productInfo.ProductParameters["OrganizationPhone"]; + + certRequest.SetCSR(csr); + + certRequest.period = Convert.ToInt32(productInfo.ProductParameters["CertificatePeriodInYears"]); + certRequest.productType = productInfo.ProductID; + certRequest.rootType = _rootType; + certRequest.slotSize = productInfo.ProductParameters.Keys.Contains("SlotSize") ? productInfo.ProductParameters["SlotSize"] : "FIVE"; + + List sans = new List(); + foreach (string[] sanValues in san.Values) + { + foreach (string sanValue in sanValues) + sans.Add(sanValue); + } + certRequest.subjectAlternativeNames = sans.ToArray(); + + string response = _api.EnrollCSR(csr, certRequest); + return JsonConvert.DeserializeObject(response); + } + + private POSTCertificateEnrollmentResponse EnrollEV(EnrollmentProductInfo productInfo, string csr, Dictionary san) + { + POSTCertificatesV1EVRequest certRequest = new POSTCertificatesV1EVRequest(); + + certRequest.contact = new ContactInfo(); + certRequest.contact.jobTitle = productInfo.ProductParameters["JobTitle"]; + certRequest.contact.email = productInfo.ProductParameters["Email"]; + certRequest.contact.nameFirst = productInfo.ProductParameters["FirstName"]; + certRequest.contact.nameLast = productInfo.ProductParameters["LastName"]; + certRequest.contact.phone = productInfo.ProductParameters["Phone"]; + + certRequest.organization = new OrganizationInfo(); + certRequest.organization.address = new AddressInfo(); + certRequest.organization.address.address1 = productInfo.ProductParameters["OrganizationAddress"]; + certRequest.organization.address.city = productInfo.ProductParameters["OrganizationCity"]; + certRequest.organization.address.state = productInfo.ProductParameters["OrganizationState"]; + certRequest.organization.address.country = productInfo.ProductParameters["OrganizationCountry"]; + certRequest.organization.name = productInfo.ProductParameters["OrganizationName"]; + certRequest.organization.phone = productInfo.ProductParameters["OrganizationPhone"]; + + certRequest.organization.jurisdictionOfIncorporation = new JurisdictionInfo(); + certRequest.organization.jurisdictionOfIncorporation.state = productInfo.ProductParameters["JurisdictionState"]; + certRequest.organization.jurisdictionOfIncorporation.country = productInfo.ProductParameters["JurisdictionCountry"]; + certRequest.organization.registrationNumber = productInfo.ProductParameters["RegistrationNumber"]; + + certRequest.SetCSR(csr); + + certRequest.period = Convert.ToInt32(productInfo.ProductParameters["CertificatePeriodInYears"]); + certRequest.productType = productInfo.ProductID; + certRequest.rootType = _rootType; + certRequest.slotSize = productInfo.ProductParameters.Keys.Contains("SlotSize") ? productInfo.ProductParameters["SlotSize"] : "FIVE"; + + List sans = new List(); + foreach (string[] sanValues in san.Values) + { + foreach (string sanValue in sanValues) + sans.Add(sanValue); + } + certRequest.subjectAlternativeNames = sans.ToArray(); + + string response = _api.EnrollCSR(csr, certRequest); + return JsonConvert.DeserializeObject(response); + } + + private POSTCertificateEnrollmentResponse RenewReissue(string certificateId, EnrollmentProductInfo productInfo, string csr, Dictionary san, bool isRenew) + { + POSTCertificateRenewalRequest certRequest = new POSTCertificateRenewalRequest(); + + certRequest.SetCSR(csr); + certRequest.rootType = _rootType; + + List sans = new List(); + foreach (string[] sanValues in san.Values) + { + foreach (string sanValue in sanValues) + sans.Add(sanValue); + } + certRequest.subjectAlternativeNames = sans.ToArray(); + + string response = _api.RenewReissueCSR(certificateId, csr, certRequest, isRenew); + return JsonConvert.DeserializeObject(response); + } + + + + - Logger.MethodExit(ILogExtensions.MethodLogLevel.Debug); return new CAConnectorCertificate() { CARequestID = caRequestID, Certificate = issuedCert, CSR = certResponse.csr, ResolutionDate = certResponse.createdAt, RevocationDate = certResponse.revokedAt, RevocationReason = null, Status = APIProcessor.MapReturnStatus(certStatus), SubmissionDate = certResponse.createdAt }; } public override int Revoke(string caRequestID, string hexSerialNumber, uint revocationReason) { Logger.MethodEntry(ILogExtensions.MethodLogLevel.Debug); try { _api.RevokeCertificate(caRequestID, APIProcessor.MapRevokeReason(revocationReason)); } catch (Exception ex) { return Convert.ToInt32(PKIConstants.Microsoft.RequestDisposition.FAILED); } Logger.MethodExit(ILogExtensions.MethodLogLevel.Debug); return Convert.ToInt32(PKIConstants.Microsoft.RequestDisposition.REVOKED); } public override void ValidateProductInfo(EnrollmentProductInfo productInfo, Dictionary connectionInfo) { Logger.MethodEntry(ILogExtensions.MethodLogLevel.Debug); Logger.MethodExit(ILogExtensions.MethodLogLevel.Debug); } #endregion #region Private Methods private string RemovePEMHeader(string pem) { - return string.IsNullOrEmpty(pem) ? string.Empty : PemUtilities.DERToPEM(PemUtilities.PEMToDER(pem), PemUtilities.PemObjectType.NoHeaders); } private void ValidateParameters(Dictionary connectionData, string[][] keysToValidate) { List errors = new List(); foreach (string[] connectionKey in keysToValidate) { if (!connectionData.ContainsKey(connectionKey[0])) errors.Add($"CAConnection configuration value {connectionKey} not found."); else if (connectionKey[1] == "int") { int value; bool isIntValue = int.TryParse(connectionData[connectionKey[0]].ToString(), out value); if (!isIntValue) errors.Add($"CAConnection configuration value {connectionKey} must contain an integer. Found {connectionData[connectionKey[0]]}"); } } if (errors.Count > 0) throw new GoDaddyException(string.Join(System.Environment.NewLine, errors.ToArray())); } private POSTCertificateEnrollmentResponse EnrollDV (EnrollmentProductInfo productInfo, string csr, Dictionary san) { POSTCertificatesV1DVRequest certRequest = new POSTCertificatesV1DVRequest(); certRequest.contact = new ContactInfo(); certRequest.contact.email = productInfo.ProductParameters["Email"]; certRequest.contact.nameFirst = productInfo.ProductParameters["FirstName"]; certRequest.contact.nameLast = productInfo.ProductParameters["LastName"]; certRequest.contact.phone = productInfo.ProductParameters["Phone"]; certRequest.SetCSR(csr); certRequest.period = Convert.ToInt32(productInfo.ProductParameters["CertificatePeriodInYears"]); certRequest.productType = productInfo.ProductID; certRequest.rootType = _rootType; certRequest.slotSize = productInfo.ProductParameters.Keys.Contains("SlotSize") ? productInfo.ProductParameters["SlotSize"] : "FIVE"; List sans = new List(); foreach (string[] sanValues in san.Values) { foreach (string sanValue in sanValues) sans.Add(sanValue); } certRequest.subjectAlternativeNames = sans.ToArray(); string response = _api.EnrollCSR(csr, certRequest); return JsonConvert.DeserializeObject(response); } private POSTCertificateEnrollmentResponse EnrollOV(EnrollmentProductInfo productInfo, string csr, Dictionary san) { POSTCertificatesV1OVRequest certRequest = new POSTCertificatesV1OVRequest(); certRequest.contact = new ContactInfo(); certRequest.contact.jobTitle = productInfo.ProductParameters["JobTitle"]; certRequest.contact.email = productInfo.ProductParameters["Email"]; certRequest.contact.nameFirst = productInfo.ProductParameters["FirstName"]; certRequest.contact.nameLast = productInfo.ProductParameters["LastName"]; certRequest.contact.phone = productInfo.ProductParameters["Phone"]; certRequest.organization = new OrganizationInfo(); certRequest.organization.address = new AddressInfo(); certRequest.organization.address.address1 = productInfo.ProductParameters["OrganizationAddress"]; certRequest.organization.address.city = productInfo.ProductParameters["OrganizationCity"]; certRequest.organization.address.state = productInfo.ProductParameters["OrganizationState"]; certRequest.organization.address.country = productInfo.ProductParameters["OrganizationCountry"]; certRequest.organization.name = productInfo.ProductParameters["OrganizationName"]; certRequest.organization.phone = productInfo.ProductParameters["OrganizationPhone"]; certRequest.SetCSR(csr); certRequest.period = Convert.ToInt32(productInfo.ProductParameters["CertificatePeriodInYears"]); certRequest.productType = productInfo.ProductID; certRequest.rootType = _rootType; certRequest.slotSize = productInfo.ProductParameters.Keys.Contains("SlotSize") ? productInfo.ProductParameters["SlotSize"] : "FIVE"; List sans = new List(); foreach (string[] sanValues in san.Values) { foreach (string sanValue in sanValues) sans.Add(sanValue); } certRequest.subjectAlternativeNames = sans.ToArray(); string response = _api.EnrollCSR(csr, certRequest); return JsonConvert.DeserializeObject(response); } private POSTCertificateEnrollmentResponse EnrollEV(EnrollmentProductInfo productInfo, string csr, Dictionary san) { POSTCertificatesV1EVRequest certRequest = new POSTCertificatesV1EVRequest(); certRequest.contact = new ContactInfo(); certRequest.contact.jobTitle = productInfo.ProductParameters["JobTitle"]; certRequest.contact.email = productInfo.ProductParameters["Email"]; certRequest.contact.nameFirst = productInfo.ProductParameters["FirstName"]; certRequest.contact.nameLast = productInfo.ProductParameters["LastName"]; certRequest.contact.phone = productInfo.ProductParameters["Phone"]; certRequest.organization = new OrganizationInfo(); certRequest.organization.address = new AddressInfo(); certRequest.organization.address.address1 = productInfo.ProductParameters["OrganizationAddress"]; certRequest.organization.address.city = productInfo.ProductParameters["OrganizationCity"]; certRequest.organization.address.state = productInfo.ProductParameters["OrganizationState"]; certRequest.organization.address.country = productInfo.ProductParameters["OrganizationCountry"]; certRequest.organization.name = productInfo.ProductParameters["OrganizationName"]; certRequest.organization.phone = productInfo.ProductParameters["OrganizationPhone"]; certRequest.organization.jurisdictionOfIncorporation = new JurisdictionInfo(); certRequest.organization.jurisdictionOfIncorporation.state = productInfo.ProductParameters["JurisdictionState"]; certRequest.organization.jurisdictionOfIncorporation.country = productInfo.ProductParameters["JurisdictionCountry"]; certRequest.organization.registrationNumber = productInfo.ProductParameters["RegistrationNumber"]; certRequest.SetCSR(csr); certRequest.period = Convert.ToInt32(productInfo.ProductParameters["CertificatePeriodInYears"]); certRequest.productType = productInfo.ProductID; certRequest.rootType = _rootType; certRequest.slotSize = productInfo.ProductParameters.Keys.Contains("SlotSize") ? productInfo.ProductParameters["SlotSize"] : "FIVE"; List sans = new List(); foreach (string[] sanValues in san.Values) { foreach (string sanValue in sanValues) sans.Add(sanValue); } certRequest.subjectAlternativeNames = sans.ToArray(); string response = _api.EnrollCSR(csr, certRequest); return JsonConvert.DeserializeObject(response); } private POSTCertificateEnrollmentResponse RenewReissue(string certificateId, EnrollmentProductInfo productInfo, string csr, Dictionary san, bool isRenew) { POSTCertificateRenewalRequest certRequest = new POSTCertificateRenewalRequest(); certRequest.SetCSR(csr); certRequest.rootType = _rootType; List sans = new List(); foreach (string[] sanValues in san.Values) { foreach (string sanValue in sanValues) sans.Add(sanValue); } certRequest.subjectAlternativeNames = sans.ToArray(); string response = _api.RenewReissueCSR(certificateId, csr, certRequest, isRenew); return JsonConvert.DeserializeObject(response); } #endregion } } \ No newline at end of file + #endregion Private Methods } } \ No newline at end of file diff --git a/README.md b/README.md index a64bf9a..917f2bf 100644 --- a/README.md +++ b/README.md @@ -49,10 +49,12 @@ GoDaddy supports the following certificate products: # Getting Started ### Prerequsites -To begin, you must have the AnyGateway Service installed and operational before attempting to configure the GoDaddy AnyGateway plugin. **INSTALLATION INSTRUCTIONS LINK** +To begin, you must have the AnyGateway Service installed and operational before attempting to configure the GoDaddy AnyGateway plugin. A production GoDaddy account must be set up that will be associated with the gateway and an API Key/Secret created. For more information on how to create an API Key, follow the instructions [here](https://developer.godaddy.com/keys). +For enrollment, make sure you have pre-purchased enough certificates of the type you will be enrolling before attempting to enroll certificates via this gateway. The gateway itself does not purchase certificates and requires that the product (certificate) be pre-purchased for the gateway to enroll it successfully. The certificate may be purchased using any payment method including but not limited to GoDaddy's Good as Gold or in store credits, but just having these funds available is not enough. The product MUST actually be pre-purchased using an available payment method. + ### Installation and Configuration ##### Step 1 - Install the GoDaddy root and intermediate certificates. @@ -167,27 +169,77 @@ After installing the Keyfactor AnyGateway service (see Prerequisites), there sho },*/ /*Information necessary to authenticate to the CA.*/ "CAConnection":{ - // Base URL for GoDaddy API calls. This value should probably not need to change from what is shown below + // Base URL for GoDaddy API calls. This value should probably not need to change + // from what is shown below "APIUrl": "https://api.ote-godaddy.com", - // The ShopperId is the "Customer #" found by selecting the pulldown on the top right of the GoDaddy portal home page - // after signing in using the account being used for the Gateway + // The ShopperId is the "Customer #" found by selecting the pulldown on the top + // right of the GoDaddy portal home page after signing in using the account + // being used for the Gateway "ShopperId": "9999999999", // The APIKey is the GoDaddy API Key and secret mentioned in "Prerequisites" "APIKey": "sso-key {large string value API Key}:{large string value API Secret}", - // One of four values based on the CA chain enrolled certificates should be validated against - GODADDY_SHA_1, GODADDY_SHA_2, - // STARTFIELD_SHA1, or STARTFIELD_SHA2 + // One of four values based on the CA chain enrolled certificates should be + // validated against - GODADDY_SHA_1, GODADDY_SHA_2, STARTFIELD_SHA1, or STARTFIELD_SHA2 "RootType": "GODADDY_SHA_2", - // The SyncPageSize represents the number of certificates that will be returned for each GoDaddy "get certificates" API call during a - // "sync" operation. The API call will be repeated in batches of this number until all cerificates are retrieved from the GoDady CA. - // GoDaddy has no imposed limit on the number of certificates that can be returned, but due to the amount of data being returned for - // each call, this number should be set to something reasonable, 50-500. - "SyncPageSize": "50", - // EnrollmentRetries is the number of tries an Enroll operation will attempt to successfully enroll a certificate (defined as a certificate - // being ISSUED or PENDING_ISSUANCE) against the GoDaddy CA before returning an error. - "EnrollmentRetries": "2", - // SecondsBetweenEnrollmentRetries is the amount of time an Enroll operation will wait between enrollment requests against the GoDaddy - // CA if the previous attempt did not produce a certificate with a status of ISSUED or PENDING_ISSUANCE. - "SecondsBetweenEnrollmentRetries": "5" + + + // The SyncPageSize represents the number of certificates that will be returned + // for each GoDaddy "get certificates" API call during a "sync" operation. + // The API call will be repeated in batches of this number until all cerificates + // are retrieved from the GoDady CA. GoDaddy has no imposed limit on the number + // of certificates that can be returned, but due to the amount of data being returned + // for each call, certificate data may need to be returned in batches. + "SyncPageSize": "50", //Default=50, Minimum=10, Maximum=1000 + + + // The following 7 settings are all optional. They each have a: 1) default value, + // 2) minimum allowed value, and 3) maximum allowed value. If any value is missing + // or outside the min/max allowed range, the default value will be used + + + // EnrollmentRetries is the number of tries an Enroll operation will attempt to successfully + // enroll a certificate (defined as a certificate being ISSUED or PENDING_ISSUANCE) + // against the GoDaddy CA before returning an error. + "EnrollmentRetries": "2", //Default=2, Minimum=0, Maximum=5 + + + // SecondsBetweenEnrollmentRetries is the amount of time an Enroll operation will wait + // between enrollment requests against the GoDaddy CA if the previous attempt did not + // produce a certificate with a status of ISSUED or PENDING_ISSUANCE. + "SecondsBetweenEnrollmentRetries": "5", //Default=5, Minimum=2, Maximum=20 + + + // ApiTimeoutInSeconds is the amount of time in seconds that a GoDaddy API request + // will wait before the call times out, producing a timeout error. + "ApiTimeoutInSeconds": "20", //Default=20, Minimum=2, Maximum=100 + + + // NumberOfCertPageRetrievalRetriesBeforeFailure is the number of times during a sync + // the retrieval of an individual page of certificates will be retried after a + // timeout before the sync will fail + "NumberOfCertPageRetrievalRetriesBeforeFailure": "2", //Default=2, Minimum=0, Maximum=10 + + + // NumberOfCertDownloadRetriesBeforeSkip is the number of times during a sync the retrieval + // of any individual certificate will be retried before that individual certificate is + // skipped if the GoDaddy API download request times out. + "NumberOfCertDownloadRetriesBeforeSkip": "2", //Default=2, Minimum=0, Maximum=10 + + + // NumberOfTimeoutsBeforeSyncFailure is the total number of timeouts all GoDaddy + // API requests can return during a sync before the sync will fail with an error + "NumberOfTimeoutsBeforeSyncFailure": "2", //Default=100, Minimum=0, Maximum=5000 + + + // MillisecondsBetweenCertDownloads is the amount of time, in milliseconds a sync + // operation will wait between GoDaddy API download requests. This is necessary + // because GoDaddy places a 60 API request per minute limit on most accounts. + // After that 60 limit has been reached, GoDaddy will begin returning + // TOO_MANY_REQUEST errors. A value of "1000" (1 second between requests) is + // recommended, but this is configurable in case an individual account allows a + // higher number of requests. + "MillisecondsBetweenCertDownloads": "1000" //Default=1000, Minimum=0, Maximum=1000 + }, /*Information to register the Gateway for client connections.*/ "GatewayRegistration":{ diff --git a/readme_source.md b/readme_source.md index de9e089..5a48062 100644 --- a/readme_source.md +++ b/readme_source.md @@ -24,10 +24,12 @@ GoDaddy supports the following certificate products: # Getting Started ### Prerequsites -To begin, you must have the AnyGateway Service installed and operational before attempting to configure the GoDaddy AnyGateway plugin. **INSTALLATION INSTRUCTIONS LINK** +To begin, you must have the AnyGateway Service installed and operational before attempting to configure the GoDaddy AnyGateway plugin. A production GoDaddy account must be set up that will be associated with the gateway and an API Key/Secret created. For more information on how to create an API Key, follow the instructions [here](https://developer.godaddy.com/keys). +For enrollment, make sure you have pre-purchased enough certificates of the type you will be enrolling before attempting to enroll certificates via this gateway. The gateway itself does not purchase certificates and requires that the product (certificate) be pre-purchased for the gateway to enroll it successfully. The certificate may be purchased using any payment method including but not limited to GoDaddy's Good as Gold or in store credits, but just having these funds available is not enough. The product MUST actually be pre-purchased using an available payment method. + ### Installation and Configuration ##### Step 1 - Install the GoDaddy root and intermediate certificates. @@ -142,27 +144,77 @@ After installing the Keyfactor AnyGateway service (see Prerequisites), there sho },*/ /*Information necessary to authenticate to the CA.*/ "CAConnection":{ - // Base URL for GoDaddy API calls. This value should probably not need to change from what is shown below + // Base URL for GoDaddy API calls. This value should probably not need to change + // from what is shown below "APIUrl": "https://api.ote-godaddy.com", - // The ShopperId is the "Customer #" found by selecting the pulldown on the top right of the GoDaddy portal home page - // after signing in using the account being used for the Gateway + // The ShopperId is the "Customer #" found by selecting the pulldown on the top + // right of the GoDaddy portal home page after signing in using the account + // being used for the Gateway "ShopperId": "9999999999", // The APIKey is the GoDaddy API Key and secret mentioned in "Prerequisites" "APIKey": "sso-key {large string value API Key}:{large string value API Secret}", - // One of four values based on the CA chain enrolled certificates should be validated against - GODADDY_SHA_1, GODADDY_SHA_2, - // STARTFIELD_SHA1, or STARTFIELD_SHA2 + // One of four values based on the CA chain enrolled certificates should be + // validated against - GODADDY_SHA_1, GODADDY_SHA_2, STARTFIELD_SHA1, or STARTFIELD_SHA2 "RootType": "GODADDY_SHA_2", - // The SyncPageSize represents the number of certificates that will be returned for each GoDaddy "get certificates" API call during a - // "sync" operation. The API call will be repeated in batches of this number until all cerificates are retrieved from the GoDady CA. - // GoDaddy has no imposed limit on the number of certificates that can be returned, but due to the amount of data being returned for - // each call, this number should be set to something reasonable, 50-500. - "SyncPageSize": "50", - // EnrollmentRetries is the number of tries an Enroll operation will attempt to successfully enroll a certificate (defined as a certificate - // being ISSUED or PENDING_ISSUANCE) against the GoDaddy CA before returning an error. - "EnrollmentRetries": "2", - // SecondsBetweenEnrollmentRetries is the amount of time an Enroll operation will wait between enrollment requests against the GoDaddy - // CA if the previous attempt did not produce a certificate with a status of ISSUED or PENDING_ISSUANCE. - "SecondsBetweenEnrollmentRetries": "5" + + + // The SyncPageSize represents the number of certificates that will be returned + // for each GoDaddy "get certificates" API call during a "sync" operation. + // The API call will be repeated in batches of this number until all cerificates + // are retrieved from the GoDady CA. GoDaddy has no imposed limit on the number + // of certificates that can be returned, but due to the amount of data being returned + // for each call, certificate data may need to be returned in batches. + "SyncPageSize": "50", //Default=50, Minimum=10, Maximum=1000 + + + // The following 7 settings are all optional. They each have a: 1) default value, + // 2) minimum allowed value, and 3) maximum allowed value. If any value is missing + // or outside the min/max allowed range, the default value will be used + + + // EnrollmentRetries is the number of tries an Enroll operation will attempt to successfully + // enroll a certificate (defined as a certificate being ISSUED or PENDING_ISSUANCE) + // against the GoDaddy CA before returning an error. + "EnrollmentRetries": "2", //Default=2, Minimum=0, Maximum=5 + + + // SecondsBetweenEnrollmentRetries is the amount of time an Enroll operation will wait + // between enrollment requests against the GoDaddy CA if the previous attempt did not + // produce a certificate with a status of ISSUED or PENDING_ISSUANCE. + "SecondsBetweenEnrollmentRetries": "5", //Default=5, Minimum=2, Maximum=20 + + + // ApiTimeoutInSeconds is the amount of time in seconds that a GoDaddy API request + // will wait before the call times out, producing a timeout error. + "ApiTimeoutInSeconds": "20", //Default=20, Minimum=2, Maximum=100 + + + // NumberOfCertPageRetrievalRetriesBeforeFailure is the number of times during a sync + // the retrieval of an individual page of certificates will be retried after a + // timeout before the sync will fail + "NumberOfCertPageRetrievalRetriesBeforeFailure": "2", //Default=2, Minimum=0, Maximum=10 + + + // NumberOfCertDownloadRetriesBeforeSkip is the number of times during a sync the retrieval + // of any individual certificate will be retried before that individual certificate is + // skipped if the GoDaddy API download request times out. + "NumberOfCertDownloadRetriesBeforeSkip": "2", //Default=2, Minimum=0, Maximum=10 + + + // NumberOfTimeoutsBeforeSyncFailure is the total number of timeouts all GoDaddy + // API requests can return during a sync before the sync will fail with an error + "NumberOfTimeoutsBeforeSyncFailure": "2", //Default=100, Minimum=0, Maximum=5000 + + + // MillisecondsBetweenCertDownloads is the amount of time, in milliseconds a sync + // operation will wait between GoDaddy API download requests. This is necessary + // because GoDaddy places a 60 API request per minute limit on most accounts. + // After that 60 limit has been reached, GoDaddy will begin returning + // TOO_MANY_REQUEST errors. A value of "1000" (1 second between requests) is + // recommended, but this is configurable in case an individual account allows a + // higher number of requests. + "MillisecondsBetweenCertDownloads": "1000" //Default=1000, Minimum=0, Maximum=1000 + }, /*Information to register the Gateway for client connections.*/ "GatewayRegistration":{