Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
4 changes: 2 additions & 2 deletions IISU/PSCertificate.cs → IISU/Certificate.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,9 @@

using System;

namespace Keyfactor.Extensions.Orchestrator.IISU
namespace Keyfactor.Extensions.Orchestrator.WindowsCertStore
{
public class PsCertificate
public class Certificate
{
public string Thumbprint { get; set; }
public byte[] RawData { get; set; }
Expand Down
226 changes: 226 additions & 0 deletions IISU/CertificateStore.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,226 @@
// Copyright 2022 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 Keyfactor.Orchestrators.Common.Enums;
using Keyfactor.Orchestrators.Extensions;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Management.Automation;
using System.Management.Automation.Runspaces;

namespace Keyfactor.Extensions.Orchestrator.WindowsCertStore
{
public class CertificateStore
{
public CertificateStore(string serverName, string storePath, Runspace runSpace)
{
ServerName = serverName;
StorePath = storePath;
RunSpace = runSpace;
Initalize();
}

public string ServerName { get; set; }
public string StorePath { get; set; }
public Runspace RunSpace { get; set; }
public List<Certificate> Certificates { get; set; }

public void RemoveCertificate(string thumbprint)
{
using var ps = PowerShell.Create();
ps.Runspace = RunSpace;
var removeScript = $@"
$ErrorActionPreference = 'Stop'
$certStore = New-Object System.Security.Cryptography.X509Certificates.X509Store('{StorePath}','LocalMachine')
$certStore.Open('MaxAllowed')
$certToRemove = $certStore.Certificates.Find(0,'{thumbprint}',$false)
if($certToRemove.Count -gt 0) {{
$certStore.Remove($certToRemove[0])
}}
$certStore.Close()
$certStore.Dispose()
";

ps.AddScript(removeScript);

var _ = ps.Invoke();
if (ps.HadErrors)
throw new CertificateStoreException($"Error removing certificate in {StorePath} store on {ServerName}.");
}

private void Initalize()
{
Certificates = new List<Certificate>();
try
{
using var ps = PowerShell.Create();
ps.Runspace = RunSpace;

var certStoreScript = $@"
$certStore = New-Object System.Security.Cryptography.X509Certificates.X509Store('{StorePath}','LocalMachine')
$certStore.Open('ReadOnly')
$certs = $certStore.Certificates
$certStore.Close()
$certStore.Dispose()
foreach ( $cert in $certs){{
$cert | Select-Object -Property Thumbprint, RawData, HasPrivateKey
}}";

ps.AddScript(certStoreScript);

var certs = ps.Invoke();

foreach (var c in certs)
Certificates.Add(new Certificate
{
Thumbprint = $"{c.Properties["Thumbprint"]?.Value}",
HasPrivateKey = bool.Parse($"{c.Properties["HasPrivateKey"]?.Value}"),
RawData = (byte[])c.Properties["RawData"]?.Value
});
}
catch (Exception ex)
{
throw new CertificateStoreException(
$"Error listing certificate in {StorePath} store on {ServerName}: {ex.Message}");
}
}

private static List<Certificate> PerformGetCertificateInvenotory(Runspace runSpace, string storePath)
{
List<Certificate> myCertificates = new List<Certificate>();
try
{
using var ps = PowerShell.Create();
ps.Runspace = runSpace;

var certStoreScript = $@"
$certStore = New-Object System.Security.Cryptography.X509Certificates.X509Store('{storePath}','LocalMachine')
$certStore.Open('ReadOnly')
$certs = $certStore.Certificates
$certStore.Close()
$certStore.Dispose()
foreach ( $cert in $certs){{
$cert | Select-Object -Property Thumbprint, RawData, HasPrivateKey
}}";

ps.AddScript(certStoreScript);

var certs = ps.Invoke();

foreach (var c in certs)
myCertificates.Add(new Certificate
{
Thumbprint = $"{c.Properties["Thumbprint"]?.Value}",
HasPrivateKey = bool.Parse($"{c.Properties["HasPrivateKey"]?.Value}"),
RawData = (byte[])c.Properties["RawData"]?.Value
});

return myCertificates;
}
catch (Exception ex)
{
throw new CertificateStoreException(
$"Error listing certificate in {storePath} store on {runSpace.ConnectionInfo.ComputerName}: {ex.Message}");
}

}

public static List<Certificate> GetCertificatesFromStore(Runspace runSpace, string storePath)
{
return PerformGetCertificateInvenotory(runSpace, storePath);
}

public static List<CurrentInventoryItem> GetIISBoundCertificates(Runspace runSpace, string storePath)
{
List<Certificate> myCertificates = PerformGetCertificateInvenotory(runSpace, storePath);
List<CurrentInventoryItem> myBoundCerts = new List<CurrentInventoryItem>();

using (var ps = PowerShell.Create())
{
ps.Runspace = runSpace;

ps.AddCommand("Import-Module")
.AddParameter("Name", "WebAdministration")
.AddStatement();

var searchScript = "Foreach($Site in get-website) { Foreach ($Bind in $Site.bindings.collection) {[pscustomobject]@{name=$Site.name;Protocol=$Bind.Protocol;Bindings=$Bind.BindingInformation;thumbprint=$Bind.certificateHash;sniFlg=$Bind.sslFlags}}}";
ps.AddScript(searchScript).AddStatement();
var iisBindings = ps.Invoke(); // Responsible for getting all bound certificates for each website

if (ps.HadErrors)
{
var psError = ps.Streams.Error.ReadAll().Aggregate(string.Empty, (current, error) => current + error.ErrorDetails.Message);
}

if (iisBindings.Count == 0)
{
return myBoundCerts;
}

//in theory should only be one, but keeping for future update to chance inventory
foreach (var binding in iisBindings)
{
var thumbPrint = $"{binding.Properties["thumbprint"]?.Value}";
if (string.IsNullOrEmpty(thumbPrint)) continue;

Certificate foundCert = myCertificates.Find(m => m.Thumbprint.Equals(thumbPrint));

if (foundCert == null) continue;

var sniValue = "";
switch (Convert.ToInt16(binding.Properties["sniFlg"]?.Value))
{
case 0:
sniValue = "0 - No SNI";
break;
case 1:
sniValue = "1 - SNI Enabled";
break;
case 2:
sniValue = "2 - Non SNI Binding";
break;
case 3:
sniValue = "3 - SNI Binding";
break;
}

var siteSettingsDict = new Dictionary<string, object>
{
{ "SiteName", binding.Properties["Name"]?.Value },
{ "Port", binding.Properties["Bindings"]?.Value.ToString()?.Split(':')[1] },
{ "IPAddress", binding.Properties["Bindings"]?.Value.ToString()?.Split(':')[0] },
{ "HostName", binding.Properties["Bindings"]?.Value.ToString()?.Split(':')[2] },
{ "SniFlag", sniValue },
{ "Protocol", binding.Properties["Protocol"]?.Value }
};

myBoundCerts.Add(
new CurrentInventoryItem
{
Certificates = new[] { foundCert.CertificateData },
Alias = thumbPrint,
PrivateKeyEntry = foundCert.HasPrivateKey,
UseChainLevel = false,
ItemStatus = OrchestratorInventoryItemStatus.Unknown,
Parameters = siteSettingsDict
}
);
}

return myBoundCerts;
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,24 +15,24 @@
using System;
using System.Runtime.Serialization;

namespace Keyfactor.Extensions.Orchestrator.IISU
namespace Keyfactor.Extensions.Orchestrator.WindowsCertStore
{
[Serializable]
internal class PsCertStoreException : Exception
internal class CertificateStoreException : Exception
{
public PsCertStoreException()
public CertificateStoreException()
{
}

public PsCertStoreException(string message) : base(message)
public CertificateStoreException(string message) : base(message)
{
}

public PsCertStoreException(string message, Exception innerException) : base(message, innerException)
public CertificateStoreException(string message, Exception innerException) : base(message, innerException)
{
}

protected PsCertStoreException(SerializationInfo info, StreamingContext context) : base(info, context)
protected CertificateStoreException(SerializationInfo info, StreamingContext context) : base(info, context)
{
}
}
Expand Down
74 changes: 74 additions & 0 deletions IISU/ClientPSCertStoreInventory.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
// Copyright 2022 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 Keyfactor.Logging;
using Microsoft.Extensions.Logging;
using System;
using System.Collections.Generic;
using System.Management.Automation;
using System.Management.Automation.Runspaces;
using System.Text;

namespace Keyfactor.Extensions.Orchestrator.WindowsCertStore
{
abstract class ClientPSCertStoreInventory
{
private ILogger _logger;
public ClientPSCertStoreInventory(ILogger logger)
{
_logger = logger;
}

public List<Certificate> GetCertificatesFromStore(Runspace runSpace, string storePath)
{
List<Certificate> myCertificates = new List<Certificate>();
try
{
using var ps = PowerShell.Create();

_logger.MethodEntry();

ps.Runspace = runSpace;

var certStoreScript = $@"
$certStore = New-Object System.Security.Cryptography.X509Certificates.X509Store('{storePath}','LocalMachine')
$certStore.Open('ReadOnly')
$certs = $certStore.Certificates
$certStore.Close()
$certStore.Dispose()
foreach ( $cert in $certs){{
$cert | Select-Object -Property Thumbprint, RawData, HasPrivateKey
}}";

ps.AddScript(certStoreScript);

var certs = ps.Invoke();

foreach (var c in certs)
myCertificates.Add(new Certificate
{
Thumbprint = $"{c.Properties["Thumbprint"]?.Value}",
HasPrivateKey = bool.Parse($"{c.Properties["HasPrivateKey"]?.Value}"),
RawData = (byte[])c.Properties["RawData"]?.Value
});

return myCertificates;
}
catch (Exception ex)
{
throw new CertificateStoreException(
$"Error listing certificate in {storePath} store on {runSpace.ConnectionInfo.ComputerName}: {ex.Message}");
}
}
}
}
Loading