Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
42dfbc4
Completed adding Integration Tests
Oct 8, 2025
ed59256
Merge pull request #154 from Keyfactor/72753-Add_Integration_Tests
rcpokorny Oct 8, 2025
4c9eaba
- Modified `Get-KFIISBoundCertificates` to use `SslFlags` directly fo…
Oct 9, 2025
607c818
Updated the code to suppress output from PowerShell execution.
Oct 9, 2025
df65fac
Added SSL Flag validation
Oct 9, 2025
a66870a
Merge pull request #156 from Keyfactor/76938_SNIFlag_Not_Reporting_Co…
rcpokorny Oct 9, 2025
dee114c
Added SAN resolver to handle entry parameters and the new SAN Property
Oct 14, 2025
afe8c79
Updated the integration-manifest to remove the SAN Entry Parameter.
Oct 30, 2025
05ad84c
Update generated docs
Oct 30, 2025
dd85978
Merge pull request #157 from Keyfactor/76023-Eliminate_But_Support_SA…
rcpokorny Nov 3, 2025
af62f1e
Initial ADFS infrastructure and modified how scripts are loaded for l…
Nov 4, 2025
a6ef607
Added ADFS Inventory Process and Unit Test
Nov 4, 2025
837596d
Added ADFS Store Type in the integration-manifest
Nov 5, 2025
d80c527
Update generated docs
Nov 5, 2025
c6d9327
Completed adding the logic to rotate the ADFS certificate and perform…
Nov 12, 2025
fe22b55
Update generated docs
Nov 12, 2025
1fe9d67
Updated the documentation to support ADFS
Nov 12, 2025
ad9e519
Merge branch '60764_Adding_ADFS_Support' of https://github.com/Keyfac…
Nov 12, 2025
31b15b6
Updated Changelog
Nov 12, 2025
40fdb7b
Merge pull request #158 from Keyfactor/60764_Adding_ADFS_Support
rcpokorny Nov 12, 2025
10f8690
Update generated docs
Nov 12, 2025
645448a
Revise SAN handling and enhance integration features
rcpokorny Nov 13, 2025
dded928
Update CHANGELOG for version 3.0.0 changes
rcpokorny Nov 13, 2025
f8cbfc3
Update generated docs
Nov 13, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 18 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,21 @@
3.0.0
* As of this version of the extension, SANs will be handled through the ODKG Enrollment page in Command, and will no longer use the SAN Entry Parameter. This version, we are removing the Entry Parameter "SAN" from the integration-manifest.json, but will still support previous versions of Command in the event the SAN Entry Parameter is passed. The next major version (4.0) will remove all support for the SAN Entry Parameter.
* Added WinADFS Store Type for rotating certificates in ADFS environments. Please note, only the service-communications certificate is rotated throughout your farm.
* Internal only: Added Integration Tests to aid in future development and testing.
* Improved messaging in the event an Entry Parameter is missing (or does not meet the casing requirements)
* Fixed the SNI/SSL flag being returned during inventory, now returns extended SSL flags
* Fixed the SNI/SSL flag when binding the certificate to allow for extended SSL flags
* Added SSL Flag validation to make sure the bit flag is correct. These are the current SSL Flags:
* 0 No SNI
* 1 SNI Enabled
* 2 Non SNI binding which uses Central Certificate Store
* 3 SNI binding which uses Central Certificate Store
* 4 Use App ID (Requires AppID parameter to be set)
* 8 Use Central Certificate Store (Non SNI)
* 32 Central Certificate Store
* 64 Disable HTTP2
* 128 Disable OCSP Stapling

2.6.3
* Fixed re-enrollment or ODKG job when RDN Components contained escaped commas.
* Updated renewal job for IIS Certs to delete the old cert if not bound or used by other web sites.
Expand Down
78 changes: 78 additions & 0 deletions IISU/Certificate.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,14 @@

// 021225 rcp 2.6.0 Cleaned up and verified code

// Ignore Spelling: Keyfactor

using Keyfactor.Logging;
using Microsoft.Extensions.Logging;
using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.IO;

namespace Keyfactor.Extensions.Orchestrator.WindowsCertStore
{
Expand Down Expand Up @@ -52,6 +57,79 @@ public static List<T> DeserializeCertificates<T>(string jsonResults)
return new List<T> { singleObject };
}
}

public static string WriteCertificateToTempPfx(string certificateContents)
{
if (string.IsNullOrWhiteSpace(certificateContents))
throw new ArgumentException("Certificate contents cannot be null or empty.", nameof(certificateContents));

try
{
// Decode the Base64 string into bytes
byte[] certBytes = Convert.FromBase64String(certificateContents);

// Create a unique temporary directory
string tempDirectory = Path.Combine(Path.GetTempPath(), "CertTemp");
Directory.CreateDirectory(tempDirectory);

// Create a unique filename
string fileName = $"cert_{Guid.NewGuid():N}.pfx";
string filePath = Path.Combine(tempDirectory, fileName);

// Write the bytes to the .pfx file
File.WriteAllBytes(filePath, certBytes);

// Return the path to the newly created file
return filePath;
}
catch (FormatException)
{
throw new InvalidDataException("The provided certificate contents are not a valid Base64 string.");
}
catch (Exception ex)
{
throw new IOException($"Failed to write certificate to temp PFX file: {ex.Message}", ex);
}
}

public static void CleanupTempCertificate(string pfxFilePath)
{
ILogger logger = LogHandler.GetClassLogger<Certificate>();

if (string.IsNullOrWhiteSpace(pfxFilePath))
return;

try
{
if (File.Exists(pfxFilePath))
{
File.Delete(pfxFilePath);
}

string? parentDir = Path.GetDirectoryName(pfxFilePath);
if (!string.IsNullOrEmpty(parentDir) && Directory.Exists(parentDir))
{
// Delete the directory if it's empty
if (Directory.GetFiles(parentDir).Length == 0 &&
Directory.GetDirectories(parentDir).Length == 0)
{
Directory.Delete(parentDir);
}
}
}
catch (IOException ioEx)
{
logger.LogWarning($"Warning: Could not delete temporary file or folder: {ioEx.Message}");
}
catch (UnauthorizedAccessException uaEx)
{
logger.LogWarning($"Warning: Access denied when cleaning up temp file: {uaEx.Message}");
}
catch (Exception ex)
{
logger.LogWarning($"Warning: Unexpected error during cleanup: {ex.Message}");
}
}
}
}
}
49 changes: 47 additions & 2 deletions IISU/ClientPSCertStoreReEnrollment.cs
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@

namespace Keyfactor.Extensions.Orchestrator.WindowsCertStore
{
internal class ClientPSCertStoreReEnrollment
public class ClientPSCertStoreReEnrollment
{
private readonly ILogger _logger;
private readonly IPAMSecretResolver _resolver;
Expand All @@ -44,6 +44,12 @@ internal class ClientPSCertStoreReEnrollment
private Collection<PSObject>? _results;
#pragma warning restore CS8632 // The annotation for nullable reference types should only be used in code within a '#nullable' annotations context.

// Empty constructor for testing purposes
public ClientPSCertStoreReEnrollment()
{
_logger = LogHandler.GetClassLogger(typeof(ClientPSCertStoreReEnrollment));
}

public ClientPSCertStoreReEnrollment(ILogger logger, IPAMSecretResolver resolver)
{
_logger = logger;
Expand All @@ -65,7 +71,11 @@ public JobResult PerformReEnrollment(ReenrollmentJobConfiguration config, Submit
var subjectText = config.JobProperties["subjectText"] as string;
var providerName = config.JobProperties["ProviderName"] as string;
var keyType = config.JobProperties["keyType"] as string;
var SAN = config.JobProperties["SAN"] as string;

// Prior to Version 3.0, SANs were passed using config.JobProperties.
// Now they are passed as a config parameter, but we will check both to maintain backward compatibility.
// Version 3.0 and greater will default to the new SANs parameter.
var SAN = ResolveSANString(config);

int keySize = 0;
if (config.JobProperties["keySize"] is not null && int.TryParse(config.JobProperties["keySize"].ToString(), out int size))
Expand Down Expand Up @@ -373,5 +383,40 @@ private string ImportCertificate(byte[] certificateRawData, string storeName)
}
}

public string ResolveSANString(ReenrollmentJobConfiguration config)
{
if (config == null)
throw new ArgumentNullException(nameof(config));

string sourceUsed;
string sanValue = string.Empty;

if (config.SANs != null && config.SANs.Count > 0)
{
var builder = new SANBuilder(config.SANs);
sanValue = builder.BuildSanString();
sourceUsed = "config.SANs (preferred)";
}
else if (config.JobProperties != null &&
config.JobProperties.TryGetValue("SAN", out object legacySanValue) &&
!string.IsNullOrWhiteSpace(legacySanValue.ToString()))
{
sanValue = legacySanValue.ToString().Trim();
sourceUsed = "config.JobProperties[\"SAN\"] (legacy)";
}
else
{
sanValue = string.Empty;
sourceUsed = "none (no SANs provided)";
}

_logger.LogTrace($"[SAN Resolver] Source used: {sourceUsed}");
if (!string.IsNullOrEmpty(sanValue))
_logger.LogTrace($"[SAN Resolver] Value: {sanValue}");
else
_logger.LogTrace("[SAN Resolver] No SAN values found.");

return sanValue;
}
}
}
Loading