diff --git a/CHANGELOG.md b/CHANGELOG.md index 1200712..7b8f82c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,3 +10,7 @@ Handle change to Sectigo API Revocation call 1.0.3 Fix for JSON serialization of revocation + +1.1.0 +Add support for using the cert upload feature to upload auth certs +Switch to .NET 8 diff --git a/README.md b/README.md index 0100805..4b14544 100644 --- a/README.md +++ b/README.md @@ -63,13 +63,16 @@ In addition, for the admin account you plan to use, make sure it has the API adm 2. On the server hosting the AnyCA Gateway REST, download and unzip the latest [Sectigo Certificate Manager Gateway AnyCA Gateway REST plugin](https://github.com/Keyfactor/sectigo-scm-caplugin/releases/latest) from GitHub. -3. Copy the unzipped directory (usually called `net6.0`) to the Extensions directory: +3. Copy the unzipped directory (usually called `net6.0` or `net8.0`) to the Extensions directory: + ```shell + Depending on your AnyCA Gateway REST version, copy the unzipped directory to one of the following locations: Program Files\Keyfactor\AnyCA Gateway\AnyGatewayREST\net6.0\Extensions + Program Files\Keyfactor\AnyCA Gateway\AnyGatewayREST\net8.0\Extensions ``` - > The directory containing the Sectigo Certificate Manager Gateway AnyCA Gateway REST plugin DLLs (`net6.0`) can be named anything, as long as it is unique within the `Extensions` directory. + > The directory containing the Sectigo Certificate Manager Gateway AnyCA Gateway REST plugin DLLs (`net6.0` or `net8.0`) can be named anything, as long as it is unique within the `Extensions` directory. 4. Restart the AnyCA Gateway REST service. diff --git a/sectigo-scm-caplugin/Client/SectigoClient.cs b/sectigo-scm-caplugin/Client/SectigoClient.cs index faefc14..f7da84f 100644 --- a/sectigo-scm-caplugin/Client/SectigoClient.cs +++ b/sectigo-scm-caplugin/Client/SectigoClient.cs @@ -1,4 +1,5 @@ -using Keyfactor.Extensions.CAPlugin.Sectigo.API; +using Keyfactor.AnyGateway.Extensions; +using Keyfactor.Extensions.CAPlugin.Sectigo.API; using Keyfactor.Extensions.CAPlugin.Sectigo.Models; using Keyfactor.Logging; @@ -134,7 +135,7 @@ public async Task CertificateListProducer(BlockingCollection certs, public async Task> PageCertificates(int position = 0, int size = 25, string filter = "") { - string filterQueryString = String.IsNullOrEmpty(filter) ? string.Empty : $"&{filter}"; + string filterQueryString = string.IsNullOrEmpty(filter) ? string.Empty : $"&{filter}"; Logger.LogTrace($"API Request: api/ssl/v1?position={position}&size={size}{filterQueryString}".TrimEnd()); var response = await RestClient.GetAsync($"api/ssl/v1?position={position}&size={size}{filterQueryString}".TrimEnd()); return await ProcessResponse>(response); @@ -305,7 +306,7 @@ private static async Task ProcessResponse(HttpResponseMessage response) } } - public static SectigoClient InitializeClient(SectigoConfig config) + public static SectigoClient InitializeClient(SectigoConfig config, ICertificateResolver certResolver) { Logger.MethodEntry(LogLevel.Debug); @@ -314,13 +315,24 @@ public static SectigoClient InitializeClient(SectigoConfig config) if (config.AuthenticationType.ToLower() == "certificate") { clientHandler.ClientCertificateOptions = ClientCertificateOption.Manual; - X509Certificate2 authCert = GetClientCertificate(config); + Logger.LogTrace($"Resolving certificate. Source: {config.Certificate.Source}"); + X509Certificate2 authCert = null; + if (!string.IsNullOrEmpty(config.Certificate.ImportedCertificate)) + { + authCert = new X509Certificate2(Convert.FromBase64String(config.Certificate.ImportedCertificate), config.Certificate.ImportedCertificatePassword); + } + else + { + authCert = certResolver.ResolveCertificate(config.Certificate); + } if (authCert == null) { Logger.MethodExit(LogLevel.Debug); throw new Exception("AuthType set to Certificate, but no certificate found!"); } + Logger.LogTrace($"Auth cert found. CERT DETAILS: \nSerial Number: {authCert.GetSerialNumberString()}\nHas PK: {authCert.HasPrivateKey.ToString()}\nSubject: {authCert.Subject}"); + clientHandler.ClientCertificates.Add(authCert); } @@ -348,58 +360,6 @@ public static SectigoClient InitializeClient(SectigoConfig config) return new SectigoClient(restClient); } - private static X509Certificate2 GetClientCertificate(SectigoConfig config) - { - Logger.MethodEntry(LogLevel.Debug); - //Dictionary caConnectionCertificateDetail = config["ClientCertificate"] as Dictionary; - X509Certificate2 clientCert = null; - - if (!string.IsNullOrEmpty(config.Certificate.Thumbprint)) - { - StoreName sn; - StoreLocation sl; - string thumbprint = config.Certificate.Thumbprint; - - if (String.IsNullOrEmpty(thumbprint) || - !Enum.TryParse(config.Certificate.StoreName, out sn) || - !Enum.TryParse(config.Certificate.StoreLocation, out sl)) - { - throw new Exception("Unable to find client authentication certificate"); - } - - X509Certificate2Collection foundCerts; - using (X509Store currentStore = new X509Store(sn, sl)) - { - Logger.LogTrace($"Search for client auth certificates with Thumprint {thumbprint} in the {sn}{sl} certificate store"); - - currentStore.Open(OpenFlags.ReadOnly | OpenFlags.OpenExistingOnly); - foundCerts = currentStore.Certificates.Find(X509FindType.FindByThumbprint, thumbprint, true); - Logger.LogTrace($"Found {foundCerts.Count} certificates in the {currentStore.Name} store"); - currentStore.Close(); - } - if (foundCerts.Count > 1) - { - throw new Exception($"Multiple certificates with Thumprint {thumbprint} found in the {sn}{sl} certificate store"); - } - if (foundCerts.Count > 0) - clientCert = foundCerts[0]; - } - else - { - // Cert is provided via pfx file instead of cert store - try - { - X509Certificate2 cert = new X509Certificate2(config.Certificate.CertificatePath, config.Certificate.CertificatePassword); - clientCert = cert; - } - catch (Exception ex) - { - throw new Exception($"Unable to open the client certificate file with the given password. Error: {ex.Message}"); - } - } - Logger.MethodExit(LogLevel.Debug); - return clientCert; - } #endregion } } diff --git a/sectigo-scm-caplugin/SectigoCAPlugin.cs b/sectigo-scm-caplugin/SectigoCAPlugin.cs index 9976a1a..c88038b 100644 --- a/sectigo-scm-caplugin/SectigoCAPlugin.cs +++ b/sectigo-scm-caplugin/SectigoCAPlugin.cs @@ -33,10 +33,12 @@ public class SectigoCAPlugin : IAnyCAPlugin private SectigoConfig _config; private readonly ILogger _logger; private ICertificateDataReader _certificateDataReader; + private ICertificateResolver _certificateResolver; - public SectigoCAPlugin() + public SectigoCAPlugin(ICertificateResolver certResolver = null) { _logger = LogHandler.GetClassLogger(); + _certificateResolver = certResolver; } public void Initialize(IAnyCAPluginConfigProvider configProvider, ICertificateDataReader certificateDataReader) @@ -88,7 +90,7 @@ public async Task Enroll(string csr, string subject, Dictionar department = productInfo.ProductParameters["Department"]; _logger.LogTrace($"Department: {department}"); } - var client = SectigoClient.InitializeClient(_config); + var client = SectigoClient.InitializeClient(_config, _certificateResolver); var fieldList = Task.Run(async () => await client.ListCustomFields()).Result; var allFields = fieldList.CustomFields?.Select(f => f); @@ -370,7 +372,7 @@ public async Task GetSingleRecord(string caRequestID) _logger.LogTrace($"Get Single Certificate Detail from Sectigo (sslId: {caRequestID})"); int sslId = int.Parse(caRequestID.Split('-')[0]); - var client = SectigoClient.InitializeClient(_config); + var client = SectigoClient.InitializeClient(_config, _certificateResolver); var singleCert = Task.Run(async () => await client.GetCertificate(sslId)).Result; _logger.LogTrace($"{singleCert.CommonName} ({singleCert.status}) retrieved from Sectigo."); @@ -446,7 +448,7 @@ public async Task Ping() try { _logger.LogDebug("Attempting to ping Sectigo API"); - var client = SectigoClient.InitializeClient(_config); + var client = SectigoClient.InitializeClient(_config, _certificateResolver); _ = Task.Run(async () => await client.ListOrganizations()).Result; } catch (Exception ex) @@ -462,7 +464,7 @@ public async Task Revoke(string caRequestID, string hexSerialNumber, uint r try { - var client = SectigoClient.InitializeClient(_config); + var client = SectigoClient.InitializeClient(_config, _certificateResolver); var response = Task.Run(async () => await client.RevokeSslCertificateById(int.Parse(caRequestID), (int)revocationReason, RevokeReasonToString(revocationReason))).Result; _logger.MethodExit(LogLevel.Debug); @@ -501,7 +503,7 @@ public async Task Synchronize(BlockingCollection blockin string[] filterProfileIds = _config.SyncFilterProfileId.Split(','); filter.Add("sslTypeId", filterProfileIds); } - var client = SectigoClient.InitializeClient(_config); + var client = SectigoClient.InitializeClient(_config, _certificateResolver); producerTask = client.CertificateListProducer(certsToAdd, newCancelToken.Token, _config.PageSize, filter); foreach (Certificate certToAdd in certsToAdd.GetConsumingEnumerable()) @@ -654,7 +656,7 @@ public async Task ValidateProductInfo(EnrollmentProductInfo productInfo, Diction _logger.MethodEntry(LogLevel.Debug); string rawConfig = JsonConvert.SerializeObject(connectionInfo); var parsedConfig = JsonConvert.DeserializeObject(rawConfig); - SectigoClient localClient = SectigoClient.InitializeClient(parsedConfig); + SectigoClient localClient = SectigoClient.InitializeClient(parsedConfig, _certificateResolver); var profileList = Task.Run(async () => await localClient.ListSslProfiles()).Result; if (profileList.SslProfiles.Where(p => p.id == int.Parse(productInfo.ProductID)).Count() == 0) @@ -667,28 +669,28 @@ public async Task ValidateProductInfo(EnrollmentProductInfo productInfo, Diction private async Task GetOrganizationAsync(string orgName) { - var client = SectigoClient.InitializeClient(_config); + var client = SectigoClient.InitializeClient(_config, _certificateResolver); var orgList = await client.ListOrganizations(); return orgList.Organizations.Where(x => x.name.ToLower().Equals(orgName.ToLower())).FirstOrDefault(); } private async Task GetProfileTerm(int profileId) { - var client = SectigoClient.InitializeClient(_config); + var client = SectigoClient.InitializeClient(_config, _certificateResolver); var profileList = await client.ListSslProfiles(); return profileList.SslProfiles.Where(x => x.id == profileId).FirstOrDefault().terms[0]; } private async Task GetProfile(int profileId) { - var client = SectigoClient.InitializeClient(_config); + var client = SectigoClient.InitializeClient(_config, _certificateResolver); var profileList = await client.ListSslProfiles(); return profileList.SslProfiles.Where(x => x.id == profileId).FirstOrDefault(); } private async Task> GetProfileIds() { - var client = SectigoClient.InitializeClient(_config); + var client = SectigoClient.InitializeClient(_config, _certificateResolver); var profileList = await client.ListSslProfiles(); return profileList.SslProfiles.Select(x => x.id).ToList(); } @@ -730,7 +732,7 @@ private async Task PickUpEnrolledCertificate(int sslId, string while (retryCounter < _config.PickupRetries) { _logger.LogDebug($"Try number {retryCounter + 1} to pickup enrolled certificate"); - var client = SectigoClient.InitializeClient(_config); + var client = SectigoClient.InitializeClient(_config, _certificateResolver); var certificate = Task.Run(async () => await client.PickupCertificate(sslId, subject)).Result; if (certificate != null && !String.IsNullOrEmpty(certificate.Subject)) { @@ -765,7 +767,7 @@ public X509Certificate2 PickupSingleCert(int sslId, string subject) while (retryCounter < _config.PickupRetries) { _logger.LogDebug($"Try number {retryCounter + 1} to pickup single certificate"); - var client = SectigoClient.InitializeClient(_config); + var client = SectigoClient.InitializeClient(_config, _certificateResolver); var certificate = Task.Run(async () => await client.PickupCertificate(sslId, subject)).Result; if (certificate != null && !String.IsNullOrEmpty(certificate.Subject)) { diff --git a/sectigo-scm-caplugin/SectigoConfig.cs b/sectigo-scm-caplugin/SectigoConfig.cs index 5dc091b..41e144a 100644 --- a/sectigo-scm-caplugin/SectigoConfig.cs +++ b/sectigo-scm-caplugin/SectigoConfig.cs @@ -1,4 +1,6 @@ -using Newtonsoft.Json; +using Keyfactor.AnyGateway.Extensions; + +using Newtonsoft.Json; using System; using System.Collections.Generic; @@ -54,13 +56,4 @@ public SectigoConfig() [JsonProperty("ClientCertificate")] public ClientCertificate Certificate { get; set; } } - - public class ClientCertificate - { - public string StoreName { get; set; } - public string StoreLocation { get; set; } - public string Thumbprint { get; set; } - public string CertificatePath { get; set; } - public string CertificatePassword { get; set; } - } } diff --git a/sectigo-scm-caplugin/sectigo-scm-caplugin.csproj b/sectigo-scm-caplugin/sectigo-scm-caplugin.csproj index f9b570d..cd841e7 100644 --- a/sectigo-scm-caplugin/sectigo-scm-caplugin.csproj +++ b/sectigo-scm-caplugin/sectigo-scm-caplugin.csproj @@ -1,7 +1,7 @@  - net6.0 + net6.0;net8.0 Keyfactor.Extensions.CAPlugin.Sectigo disable disable @@ -10,7 +10,7 @@ - +