Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merge 2.9.0 to main #70

Merged
merged 2 commits into from
Nov 1, 2024
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
3 changes: 2 additions & 1 deletion .github/workflows/keyfactor-starter-workflow.yml
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,10 @@ on:

jobs:
call-starter-workflow:
uses: keyfactor/actions/.github/workflows/starter.yml@v2
uses: keyfactor/actions/.github/workflows/starter.yml@3.1.1
secrets:
token: ${{ secrets.V2BUILDTOKEN}}
APPROVE_README_PUSH: ${{ secrets.APPROVE_README_PUSH}}
gpg_key: ${{ secrets.KF_GPG_PRIVATE_KEY }}
gpg_pass: ${{ secrets.KF_GPG_PASSPHRASE }}
scan_token: ${{ secrets.SAST_TOKEN }}
7 changes: 7 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,10 @@
v2.9.0
- Modify Discovery on Linux servers to filter out ignored folders when searching using the Find command rather than eliminating them after. This was done to eliminate permissions errors.
- Deprecated isRSAPrivateKey custom property on RFPEM certificate store type. Integration now reads the existing private key to determin if it is formatted as PKCS#1 or PKCS#8 and, on renewal, keeps the format the same. For new PEM certificate stores/certificates, PKCS#8 will always be used. PLEASE NOTE, for existing certificate stores that already have isRSAPrivateKey defined, this setting will be ignored.
- Modified RFPkcs12 store type to handle single store certificate stores with no friendly name/alias
- Modify to create 2 builds - one for .net6 and one for .net8
- Update README to new DocTool format

v2.8.1
- Fixed issue were sensitive information could be exposed at debug logging level

Expand Down
1,482 changes: 1,139 additions & 343 deletions README.md

Large diffs are not rendered by default.

4 changes: 3 additions & 1 deletion RemoteFile/Discovery.cs
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@

using Microsoft.Extensions.Logging;
using Newtonsoft.Json;
using static Keyfactor.PKI.PKIConstants.Microsoft;

namespace Keyfactor.Extensions.Orchestrator.RemoteFile
{
Expand Down Expand Up @@ -67,7 +68,8 @@ public JobResult ProcessJob(DiscoveryJobConfiguration config, SubmitDiscoveryUpd
if (filesTosearch.Length == 0)
filesTosearch = new string[] { "*" };

locations = certificateStore.FindStores(directoriesToSearch, extensionsToSearch, filesTosearch, includeSymLinks);
locations = certificateStore.FindStores(directoriesToSearch, extensionsToSearch, filesTosearch, ignoredDirs, includeSymLinks);

foreach (string ignoredDir in ignoredDirs)
{
locations = locations.Where(p => !p.StartsWith(ignoredDir) && !p.ToLower().StartsWith("find:")).ToList();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -51,9 +51,9 @@ public Pkcs12Store DeserializeRemoteCertificateStore(byte[] storeContentBytes, s
byte[] storeBytes = remoteHandler.DownloadCertificateFile($"{storePath}{tempCertFile}");
store.Load(new MemoryStream(storeBytes), string.IsNullOrEmpty(storePassword) ? new char[0] : storePassword.ToCharArray());
}
catch (Exception ex)
catch (Exception)
{
throw ex;
throw;
}
finally
{
Expand Down Expand Up @@ -90,9 +90,9 @@ public List<SerializedStoreInfo> SerializeRemoteCertificateStore(Pkcs12Store cer
storeInfo.Add(new SerializedStoreInfo() { Contents = storeContents, FilePath = storePath+storeFileName });
return storeInfo;
}
catch (Exception ex)
catch (Exception)
{
throw ex;
throw;
}
finally
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -59,9 +59,9 @@ public Pkcs12Store DeserializeRemoteCertificateStore(byte[] storeContentBytes, s
JKSCertificateStoreSerializer serializer = new JKSCertificateStoreSerializer(String.Empty);
store = serializer.DeserializeRemoteCertificateStore(storeBytes, $"{WorkFolder}{tempStoreFileJKS}", storePassword, remoteHandler, isInventory);
}
catch (Exception ex)
catch (Exception)
{
throw ex;
throw;
}
finally
{
Expand Down Expand Up @@ -101,9 +101,9 @@ public List<SerializedStoreInfo> SerializeRemoteCertificateStore(Pkcs12Store cer
storeInfo.Add(new SerializedStoreInfo() { Contents = storeContents, FilePath = storePath+storeFileName });
return storeInfo;
}
catch (Exception ex)
catch (Exception)
{
throw ex;
throw;
}
finally
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,6 @@ class PEMCertificateStoreSerializer : ICertificateStoreSerializer
private bool IsTrustStore { get; set; }
private bool IncludesChain { get; set; }
private string SeparatePrivateKeyFilePath { get; set; }
private bool IsRSAPrivateKey { get; set; }
private bool IgnorePrivateKeyOnInventory { get; set; }

private ILogger logger;
Expand All @@ -53,9 +52,6 @@ public PEMCertificateStoreSerializer(string storeProperties)
public Pkcs12Store DeserializeRemoteCertificateStore(byte[] storeContentBytes, string storePath, string storePassword, IRemoteHandler remoteHandler, bool isInventory)
{
logger.MethodEntry(LogLevel.Debug);

if (IsRSAPrivateKey && !string.IsNullOrEmpty(storePassword))
throw new RemoteFileException($"Certificate store with an RSA Private Key cannot contain a store password. Invalid store format not supported.");

Pkcs12StoreBuilder storeBuilder = new Pkcs12StoreBuilder();
Pkcs12Store store = storeBuilder.Build();
Expand All @@ -72,7 +68,12 @@ public Pkcs12Store DeserializeRemoteCertificateStore(byte[] storeContentBytes, s
}
else
{
AsymmetricKeyEntry keyEntry = GetPrivateKey(storeContents, storePassword ?? string.Empty, remoteHandler);
bool isRSAPrivateKey = false;
AsymmetricKeyEntry keyEntry = GetPrivateKey(storeContents, storePassword ?? string.Empty, remoteHandler, out isRSAPrivateKey);

if (isRSAPrivateKey && !string.IsNullOrEmpty(storePassword))
throw new RemoteFileException($"Certificate store with an RSA Private Key cannot contain a store password. Invalid store format not supported.");

store.SetKeyEntry(CertificateConverterFactory.FromBouncyCastleCertificate(certificates[0].Certificate).ToX509Certificate2().Thumbprint, keyEntry, certificates);
}

Expand All @@ -93,9 +94,6 @@ public List<SerializedStoreInfo> SerializeRemoteCertificateStore(Pkcs12Store cer
{
logger.MethodEntry(LogLevel.Debug);

if (IsRSAPrivateKey && !string.IsNullOrEmpty(storePassword))
throw new RemoteFileException($"Certificate store with an RSA Private Key cannot contain a store password. Invalid store format not supported.");

string pemString = string.Empty;
string keyString = string.Empty;
List<SerializedStoreInfo> storeInfo = new List<SerializedStoreInfo>();
Expand All @@ -113,6 +111,17 @@ public List<SerializedStoreInfo> SerializeRemoteCertificateStore(Pkcs12Store cer
}
else
{
string storeContents = Encoding.ASCII.GetString(remoteHandler.DownloadCertificateFile(storePath + storeFileName));
bool isRSAPrivateKey = false;
try
{
GetPrivateKey(storeContents, storePassword, remoteHandler, out isRSAPrivateKey);
}
catch (RemoteFileException) { }

if (isRSAPrivateKey && !string.IsNullOrEmpty(storePassword))
throw new RemoteFileException($"Certificate store with an RSA Private Key cannot contain a store password. Invalid store format not supported.");

bool keyEntryProcessed = false;
foreach (string alias in certificateStore.Aliases)
{
Expand All @@ -131,7 +140,7 @@ public List<SerializedStoreInfo> SerializeRemoteCertificateStore(Pkcs12Store cer
X509CertificateEntry[] certEntries = certificateStore.GetCertificateChain(alias);
AsymmetricKeyParameter publicKey = certEntries[0].Certificate.GetPublicKey();

if (IsRSAPrivateKey)
if (isRSAPrivateKey)
{
TextWriter textWriter = new StringWriter();
PemWriter pemWriter = new PemWriter(textWriter);
Expand Down Expand Up @@ -185,7 +194,6 @@ private void LoadCustomProperties(string storeProperties)
IsTrustStore = properties.IsTrustStore == null || string.IsNullOrEmpty(properties.IsTrustStore.Value) ? false : bool.Parse(properties.IsTrustStore.Value);
IncludesChain = properties.IncludesChain == null || string.IsNullOrEmpty(properties.IncludesChain.Value) ? false : bool.Parse(properties.IncludesChain.Value);
SeparatePrivateKeyFilePath = properties.SeparatePrivateKeyFilePath == null || string.IsNullOrEmpty(properties.SeparatePrivateKeyFilePath.Value) ? String.Empty : properties.SeparatePrivateKeyFilePath.Value;
IsRSAPrivateKey = properties.IsRSAPrivateKey == null || string.IsNullOrEmpty(properties.IsRSAPrivateKey.Value) ? false : bool.Parse(properties.IsRSAPrivateKey.Value);
IgnorePrivateKeyOnInventory = properties.IgnorePrivateKeyOnInventory == null || string.IsNullOrEmpty(properties.IgnorePrivateKeyOnInventory.Value) ? false : bool.Parse(properties.IgnorePrivateKeyOnInventory.Value);

logger.MethodExit(LogLevel.Debug);
Expand Down Expand Up @@ -222,7 +230,7 @@ private X509CertificateEntry[] GetCertificates(string certificates)
return certificateEntries.ToArray();
}

private AsymmetricKeyEntry GetPrivateKey(string storeContents, string storePassword, IRemoteHandler remoteHandler)
private AsymmetricKeyEntry GetPrivateKey(string storeContents, string storePassword, IRemoteHandler remoteHandler, out bool isRSA)
{
logger.MethodEntry(LogLevel.Debug);

Expand All @@ -231,8 +239,18 @@ private AsymmetricKeyEntry GetPrivateKey(string storeContents, string storePassw
storeContents = Encoding.ASCII.GetString(remoteHandler.DownloadCertificateFile(SeparatePrivateKeyFilePath));
}

isRSA = false;
foreach (string begDelim in PrivateKeyDelimetersPkcs1)
{
if (storeContents.Contains(begDelim))
{
isRSA = true;
break;
}
}

string privateKey = string.Empty;
foreach (string begDelim in IsRSAPrivateKey ? PrivateKeyDelimetersPkcs1 : PrivateKeyDelimetersPkcs8)
foreach (string begDelim in isRSA ? PrivateKeyDelimetersPkcs1 : PrivateKeyDelimetersPkcs8)
{
string endDelim = begDelim.Replace("BEGIN", "END");

Expand All @@ -252,7 +270,7 @@ private AsymmetricKeyEntry GetPrivateKey(string storeContents, string storePassw
throw new RemoteFileException("Invalid private key: No private key or invalid private key format found.");

PrivateKeyConverter c;
if (IsRSAPrivateKey)
if (isRSA)
{
RSA rsa = RSA.Create();
int bytesRead;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
// 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;
using System.IO;
using System.Collections.Generic;
using Keyfactor.Extensions.Orchestrator.RemoteFile.RemoteHandlers;
Expand All @@ -13,12 +14,15 @@
using Org.BouncyCastle.Pkcs;
using Keyfactor.Logging;
using Microsoft.Extensions.Logging;
using System.Linq;
using Keyfactor.PKI.Extensions;

namespace Keyfactor.Extensions.Orchestrator.RemoteFile.PKCS12
{
class PKCS12CertificateStoreSerializer : ICertificateStoreSerializer
{
private ILogger logger;
private bool HasEmptyAliases { get; set; }

public PKCS12CertificateStoreSerializer(string storeProperties)
{
Expand All @@ -28,21 +32,46 @@ public PKCS12CertificateStoreSerializer(string storeProperties)
public Pkcs12Store DeserializeRemoteCertificateStore(byte[] storeContents, string storePath, string storePassword, IRemoteHandler remoteHandler, bool isInventory)
{
Pkcs12StoreBuilder storeBuilder = new Pkcs12StoreBuilder();
Pkcs12Store store = storeBuilder.Build();
Pkcs12Store workingStore = storeBuilder.Build();
Pkcs12Store returnStore = storeBuilder.Build();

using (MemoryStream ms = new MemoryStream(storeContents))
{
store.Load(ms, string.IsNullOrEmpty(storePassword) ? new char[0] : storePassword.ToCharArray());
workingStore.Load(ms, string.IsNullOrEmpty(storePassword) ? new char[0] : storePassword.ToCharArray());
}

return store;
if (workingStore.Aliases.Where(p => string.IsNullOrEmpty(p)).Count() > 0 && workingStore.Aliases.Where(p => !string.IsNullOrEmpty(p)).Count() > 0)
throw new Exception("Certificate store contains entries with both empty and non-empty friendly names. This configuration is not supported in this store type.");

HasEmptyAliases = workingStore.Aliases.Where(p => string.IsNullOrEmpty(p)).Count() > 0;

returnStore = ConvertAliases(workingStore, true);

return returnStore;
}

public List<SerializedStoreInfo> SerializeRemoteCertificateStore(Pkcs12Store certificateStore, string storePath, string storeFileName, string storePassword, IRemoteHandler remoteHandler)
{
Pkcs12StoreBuilder storeBuilder = new Pkcs12StoreBuilder();
Pkcs12Store workingStore = storeBuilder.Build();

foreach (string alias in certificateStore.Aliases)
{
if (certificateStore.IsKeyEntry(alias))
{
workingStore.SetKeyEntry(alias, certificateStore.GetKey(alias), certificateStore.GetCertificateChain(alias));
}
else
{
workingStore.SetCertificateEntry(alias, certificateStore.GetCertificate(alias));
}
}

Pkcs12Store outputCertificateStore = ConvertAliases(workingStore, false);

using (MemoryStream outStream = new MemoryStream())
{
certificateStore.Save(outStream, string.IsNullOrEmpty(storePassword) ? new char[0] : storePassword.ToCharArray(), new Org.BouncyCastle.Security.SecureRandom());
outputCertificateStore.Save(outStream, string.IsNullOrEmpty(storePassword) ? new char[0] : storePassword.ToCharArray(), new Org.BouncyCastle.Security.SecureRandom());

List<SerializedStoreInfo> storeInfo = new List<SerializedStoreInfo>();
storeInfo.Add(new SerializedStoreInfo() { FilePath = storePath+storeFileName, Contents = outStream.ToArray() });
Expand All @@ -55,5 +84,34 @@ public string GetPrivateKeyPath()
{
return null;
}

private Pkcs12Store ConvertAliases(Pkcs12Store workingStore, bool useThumbprintAsAlias)
{
Pkcs12StoreBuilder storeBuilder = new Pkcs12StoreBuilder();
Pkcs12Store returnStore = storeBuilder.Build();

if (HasEmptyAliases)
{
foreach (string alias in workingStore.Aliases)
{
if (workingStore.IsKeyEntry(alias))
{
X509CertificateEntry cert = workingStore.GetCertificate(alias);
returnStore.SetKeyEntry(useThumbprintAsAlias ? cert.Certificate.Thumbprint() : string.Empty, workingStore.GetKey(alias), workingStore.GetCertificateChain(alias));
}
else
{
X509CertificateEntry cert = workingStore.GetCertificate(alias);
returnStore.SetCertificateEntry(cert.Certificate.Thumbprint(), cert);
}
}
}
else
{
returnStore = workingStore;
}

return returnStore;
}
}
}
2 changes: 1 addition & 1 deletion RemoteFile/InventoryBase.cs
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ public JobResult ProcessJob(InventoryJobConfiguration config, SubmitInventoryUpd
logger.LogDebug($"Begin {config.Capability} for job id {config.JobId}...");
logger.LogDebug($"Server: { config.CertificateStoreDetails.ClientMachine }");
logger.LogDebug($"Store Path: { config.CertificateStoreDetails.StorePath }");
logger.LogDebug($"Job Properties:");
logger.LogDebug($"Job Properties:");
foreach (KeyValuePair<string, object> keyValue in config.JobProperties ?? new Dictionary<string,object>())
{
logger.LogDebug($" {keyValue.Key}: {keyValue.Value}");
Expand Down
2 changes: 1 addition & 1 deletion RemoteFile/ManagementBase.cs
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ public JobResult ProcessJob(ManagementJobConfiguration config)
ILogger logger = LogHandler.GetClassLogger(this.GetType());
logger.LogDebug($"Begin {config.Capability} for job id {config.JobId}...");
logger.LogDebug($"Server: {config.CertificateStoreDetails.ClientMachine}");
logger.LogDebug($"Store Path: {config.CertificateStoreDetails.StorePath}");
logger.LogDebug($"Store Path: {config.CertificateStoreDetails.StorePath}");
logger.LogDebug($"Job Properties:");
foreach (KeyValuePair<string, object> keyValue in config.JobProperties == null ? new Dictionary<string, object>() : config.JobProperties)
{
Expand Down
2 changes: 1 addition & 1 deletion RemoteFile/Models/SerializedStoreInfo.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@

namespace Keyfactor.Extensions.Orchestrator.RemoteFile.Models
{
class SerializedStoreInfo : X509Certificate2
internal class SerializedStoreInfo
{
public string FilePath { get; set; }

Expand Down
12 changes: 9 additions & 3 deletions RemoteFile/RemoteCertificateStore.cs
Original file line number Diff line number Diff line change
Expand Up @@ -137,7 +137,7 @@ internal void Terminate()
logger.MethodExit(LogLevel.Debug);
}

internal List<string> FindStores(string[] paths, string[] extensions, string[] files, bool includeSymLinks)
internal List<string> FindStores(string[] paths, string[] extensions, string[] files, string[] ignoredDirs, bool includeSymLinks)
{
logger.MethodEntry(LogLevel.Debug);

Expand All @@ -153,7 +153,7 @@ internal List<string> FindStores(string[] paths, string[] extensions, string[] f
if (DiscoveredStores != null)
return DiscoveredStores;

return ServerType == ServerTypeEnum.Linux ? FindStoresLinux(paths, extensions, files, includeSymLinks) : FindStoresWindows(paths, extensions, files);
return ServerType == ServerTypeEnum.Linux ? FindStoresLinux(paths, extensions, files, ignoredDirs, includeSymLinks) : FindStoresWindows(paths, extensions, files);
}

internal List<X509Certificate2Collection> GetCertificateChains()
Expand Down Expand Up @@ -511,14 +511,20 @@ private bool IsValueSafeRegex(string value)
return regex.IsMatch(value);
}

private List<string> FindStoresLinux(string[] paths, string[] extensions, string[] fileNames, bool includeSymLinks)
private List<string> FindStoresLinux(string[] paths, string[] extensions, string[] fileNames, string[] ignoredDirs, bool includeSymLinks)
{
logger.MethodEntry(LogLevel.Debug);

try
{
string concatPaths = string.Join(" ", paths);
string command = $"find {concatPaths} -path /proc -prune -o ";

foreach (string ignoredDir in ignoredDirs)
{
command += $"-path {ignoredDir} -prune -o ";
}

if (!includeSymLinks)
command += " -type f ";

Expand Down
Loading
Loading