Skip to content

Commit 4e0f8c8

Browse files
authored
Merge 973e015 into 7930e17
2 parents 7930e17 + 973e015 commit 4e0f8c8

37 files changed

+453
-344
lines changed

.github/workflows/keyfactor-merge-store-types.yml

Lines changed: 0 additions & 27 deletions
This file was deleted.

.github/workflows/keyfactor-starter-workflow.yml

Lines changed: 13 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -11,10 +11,17 @@ on:
1111

1212
jobs:
1313
call-starter-workflow:
14-
uses: keyfactor/actions/.github/workflows/[email protected]
14+
uses: keyfactor/actions/.github/workflows/starter.yml@v4
15+
with:
16+
command_token_url: ${{ vars.COMMAND_TOKEN_URL }} # Only required for doctool generated screenshots
17+
command_hostname: ${{ vars.COMMAND_HOSTNAME }} # Only required for doctool generated screenshots
18+
command_base_api_path: ${{ vars.COMMAND_API_PATH }} # Only required for doctool generated screenshots
1519
secrets:
16-
token: ${{ secrets.V2BUILDTOKEN}}
17-
APPROVE_README_PUSH: ${{ secrets.APPROVE_README_PUSH}}
18-
gpg_key: ${{ secrets.KF_GPG_PRIVATE_KEY }}
19-
gpg_pass: ${{ secrets.KF_GPG_PASSPHRASE }}
20-
scan_token: ${{ secrets.SAST_TOKEN }}
20+
token: ${{ secrets.V2BUILDTOKEN}} # REQUIRED
21+
gpg_key: ${{ secrets.KF_GPG_PRIVATE_KEY }} # Only required for golang builds
22+
gpg_pass: ${{ secrets.KF_GPG_PASSPHRASE }} # Only required for golang builds
23+
scan_token: ${{ secrets.SAST_TOKEN }} # REQUIRED
24+
entra_username: ${{ secrets.DOCTOOL_ENTRA_USERNAME }} # Only required for doctool generated screenshots
25+
entra_password: ${{ secrets.DOCTOOL_ENTRA_PASSWD }} # Only required for doctool generated screenshots
26+
command_client_id: ${{ secrets.COMMAND_CLIENT_ID }} # Only required for doctool generated screenshots
27+
command_client_secret: ${{ secrets.COMMAND_CLIENT_SECRET }} # Only required for doctool generated screenshots

CHANGELOG.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
1+
v2.12.0
2+
- Added config.json setting and its override store level custom field - AllowShellCommands. If "N" (default "Y"), SFTP will be used to create stores and move files on Linux-based certificate store servers. No Linux shell commands will be used in the integration.
3+
14
v2.11.5
25
- Bug Fix: Rare race condition loading config settings when multiple RemoteFile jobs are running simultaneously on the same orchestrator
36
- Documentation update to better list out what Linux commands get executed under what situations in Requirements & Prerequisites section

README.md

Lines changed: 44 additions & 1 deletion
Large diffs are not rendered by default.

RemoteFile.UnitTests/RemoteFile.UnitTests.csproj

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,18 @@
11
<Project Sdk="Microsoft.NET.Sdk">
22

33
<PropertyGroup>
4-
<TargetFramework>net6.0</TargetFramework>
4+
<TargetFramework>net8.0</TargetFramework>
55
<ImplicitUsings>enable</ImplicitUsings>
66
<Nullable>enable</Nullable>
77

88
<IsPackable>false</IsPackable>
99
</PropertyGroup>
1010

1111
<ItemGroup>
12-
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.1.0"/>
13-
<PackageReference Include="xunit" Version="2.4.1"/>
12+
<PackageReference Include="BouncyCastle.Cryptography" Version="2.6.2" />
13+
<PackageReference Include="Keyfactor.PKI" Version="8.1.1" />
14+
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.1.0" />
15+
<PackageReference Include="xunit" Version="2.4.1" />
1416
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.3">
1517
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
1618
<PrivateAssets>all</PrivateAssets>

RemoteFile/ApplicationSettings.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ public enum FileTransferProtocolEnum
4242
public static string DefaultSudoImpersonatedUser { get { return configuration.ContainsKey("DefaultSudoImpersonatedUser") ? configuration["DefaultSudoImpersonatedUser"] : DEFAULT_SUDO_IMPERSONATION_SETTING; } }
4343
public static bool CreateCSROnDevice { get { return configuration.ContainsKey("CreateCSROnDevice") ? configuration["CreateCSROnDevice"]?.ToUpper() == "Y" : false; } }
4444
public static string TempFilePathForODKG { get { return configuration.ContainsKey("TempFilePathForODKG") ? configuration["TempFilePathForODKG"] : string.Empty; } }
45+
public static bool UseShellCommands { get { return configuration.ContainsKey("UseShellCommands") ? configuration["UseShellCommands"]?.ToUpper() == "Y" : true; } }
4546
public static int SSHPort
4647
{
4748
get

RemoteFile/Discovery.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,7 @@ public JobResult ProcessJob(DiscoveryJobConfiguration config, SubmitDiscoveryUpd
5757
string userPassword = PAMUtilities.ResolvePAMField(_resolver, logger, "Server Password", config.ServerPassword);
5858

5959
certificateStore = new RemoteCertificateStore(config.ClientMachine, userName, userPassword, directoriesToSearch[0].Substring(0, 1) == "/" ? RemoteCertificateStore.ServerTypeEnum.Linux : RemoteCertificateStore.ServerTypeEnum.Windows, ApplicationSettings.SSHPort);
60-
certificateStore.Initialize(ApplicationSettings.DefaultSudoImpersonatedUser);
60+
certificateStore.Initialize(ApplicationSettings.DefaultSudoImpersonatedUser, true);
6161

6262
if (directoriesToSearch.Length == 0)
6363
throw new RemoteFileException("Blank or missing search directories for Discovery.");

RemoteFile/ImplementedStoreTypes/PEM/PEMCertificateStoreSerializer.cs

Lines changed: 33 additions & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -5,30 +5,28 @@
55
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions
66
// and limitations under the License.
77

8-
using System;
9-
using System.Collections.Generic;
10-
using System.Text;
11-
using System.IO;
12-
13-
using Newtonsoft.Json;
14-
8+
using Keyfactor.Extensions.Orchestrator.RemoteFile.Models;
9+
using Keyfactor.Extensions.Orchestrator.RemoteFile.RemoteHandlers;
1510
using Keyfactor.Logging;
16-
using Keyfactor.PKI.PrivateKeys;
17-
using Keyfactor.PKI.X509;
11+
using Keyfactor.PKI.CryptographicObjects.Formatters;
12+
using Keyfactor.PKI.Extensions;
1813
using Keyfactor.PKI.PEM;
19-
using Keyfactor.Extensions.Orchestrator.RemoteFile.RemoteHandlers;
20-
using Keyfactor.Extensions.Orchestrator.RemoteFile.Models;
21-
14+
using Keyfactor.PKI.PrivateKeys;
2215
using Microsoft.Extensions.Logging;
23-
24-
using Org.BouncyCastle.Math;
16+
using Newtonsoft.Json;
17+
using Org.BouncyCastle.Asn1.X9;
2518
using Org.BouncyCastle.Crypto;
19+
using Org.BouncyCastle.Crypto.Parameters;
20+
using Org.BouncyCastle.Math;
21+
using Org.BouncyCastle.OpenSsl;
2622
using Org.BouncyCastle.Pkcs;
2723
using Org.BouncyCastle.X509;
24+
using System;
25+
using System.Collections.Generic;
26+
using System.IO;
27+
using System.Linq;
2828
using System.Security.Cryptography;
29-
using Org.BouncyCastle.OpenSsl;
30-
using Org.BouncyCastle.Crypto.Parameters;
31-
using Org.BouncyCastle.Asn1.X9;
29+
using System.Text;
3230

3331
namespace Keyfactor.Extensions.Orchestrator.RemoteFile.PEM
3432
{
@@ -37,8 +35,6 @@ class PEMCertificateStoreSerializer : ICertificateStoreSerializer
3735
string[] PrivateKeyDelimetersPkcs8 = new string[] { "-----BEGIN PRIVATE KEY-----", "-----BEGIN ENCRYPTED PRIVATE KEY-----" };
3836
string[] PrivateKeyDelimetersRSA = new string[] { "-----BEGIN RSA PRIVATE KEY-----" };
3937
string[] PrivateKeyDelimetersEC = new string[] { "-----BEGIN EC PRIVATE KEY-----" };
40-
string CertDelimBeg = "-----BEGIN CERTIFICATE-----";
41-
string CertDelimEnd = "-----END CERTIFICATE-----";
4238

4339
private enum PrivateKeyTypeEnum
4440
{
@@ -74,15 +70,15 @@ public Pkcs12Store DeserializeRemoteCertificateStore(byte[] storeContentBytes, s
7470
{
7571
foreach(X509CertificateEntry certificate in certificates)
7672
{
77-
store.SetCertificateEntry(CertificateConverterFactory.FromBouncyCastleCertificate(certificate.Certificate).ToX509Certificate2().Thumbprint, certificate);
73+
store.SetCertificateEntry(certificate.Certificate.Thumbprint(), certificate);
7874
}
7975
}
8076
else
8177
{
8278
PrivateKeyTypeEnum privateKeyType;
8379
AsymmetricKeyEntry keyEntry = GetPrivateKey(storeContents, storePassword ?? string.Empty, remoteHandler, out privateKeyType);
8480

85-
store.SetKeyEntry(CertificateConverterFactory.FromBouncyCastleCertificate(certificates[0].Certificate).ToX509Certificate2().Thumbprint, keyEntry, certificates);
81+
store.SetKeyEntry(certificates[0].Certificate.Thumbprint(), keyEntry, certificates);
8682
}
8783

8884
// Second Pkcs12Store necessary because of an obscure BC bug where creating a Pkcs12Store without .Load (code above using "Set" methods only) does not set all internal hashtables necessary to avoid an error later
@@ -113,8 +109,7 @@ public List<SerializedStoreInfo> SerializeRemoteCertificateStore(Pkcs12Store cer
113109
if (certificateStore.IsKeyEntry(alias))
114110
throw new RemoteFileException("Cannot add a certificate with a private key to a PEM trust store.");
115111

116-
CertificateConverter certConverter = CertificateConverterFactory.FromBouncyCastleCertificate(certificateStore.GetCertificate(alias).Certificate);
117-
pemString += certConverter.ToPEM(true);
112+
pemString += CryptographicObjectFormatter.PEM.Format(certificateStore.GetCertificate(alias).Certificate, false);
118113
}
119114
}
120115
else
@@ -140,44 +135,29 @@ public List<SerializedStoreInfo> SerializeRemoteCertificateStore(Pkcs12Store cer
140135
throw new RemoteFileException("No private key found. Private key must be present to add entry to a non-Trust PEM certificate store.");
141136

142137
X509CertificateEntry[] chainEntries = certificateStore.GetCertificateChain(alias);
143-
CertificateConverter certConverter = CertificateConverterFactory.FromBouncyCastleCertificate(chainEntries[0].Certificate);
138+
X509Certificate endCertificate = chainEntries[0].Certificate;
144139

145140
AsymmetricKeyParameter privateKey = certificateStore.GetKey(alias).Key;
146-
AsymmetricKeyParameter publicKey = chainEntries[0].Certificate.GetPublicKey();
141+
PrivateKeyConverter keyConverter = PrivateKeyConverterFactory.FromBCPrivateKeyAndCert(privateKey, endCertificate);
147142

148-
if (privateKeyType == PrivateKeyTypeEnum.PKCS8)
149-
{
150-
PrivateKeyConverter keyConverter = PrivateKeyConverterFactory.FromBCKeyPair(privateKey, publicKey, false);
143+
keyString = CryptographicObjectFormatter.PEM.Format(keyConverter, storePassword);
144+
pemString = string.IsNullOrEmpty(SeparatePrivateKeyFilePath)
145+
? CryptographicObjectFormatter.PEM.Format(endCertificate, keyConverter, storePassword, false)
146+
: CryptographicObjectFormatter.PEM.Format(endCertificate, false);
151147

152-
byte[] privateKeyBytes = string.IsNullOrEmpty(storePassword) ? keyConverter.ToPkcs8BlobUnencrypted() : keyConverter.ToPkcs8Blob(storePassword);
153-
keyString = PemUtilities.DERToPEM(privateKeyBytes, string.IsNullOrEmpty(storePassword) ? PemUtilities.PemObjectType.PrivateKey : PemUtilities.PemObjectType.EncryptedPrivateKey);
154-
}
155-
else
148+
if (!IncludesChain)
156149
{
157-
TextWriter textWriter = new StringWriter();
158-
PemWriter pemWriter = new PemWriter(textWriter);
159-
pemWriter.WriteObject(privateKey);
160-
pemWriter.Writer.Flush();
161-
162-
keyString = textWriter.ToString();
150+
continue;
163151
}
164152

165-
pemString = certConverter.ToPEM(true);
166-
if (string.IsNullOrEmpty(SeparatePrivateKeyFilePath))
167-
pemString += keyString;
168-
169-
if (IncludesChain)
153+
for (int i = 1; i < chainEntries.Length; i++)
170154
{
171-
for (int i = 1; i < chainEntries.Length; i++)
172-
{
173-
CertificateConverter chainConverter = CertificateConverterFactory.FromBouncyCastleCertificate(chainEntries[i].Certificate);
174-
pemString += chainConverter.ToPEM(true);
175-
}
155+
pemString += CryptographicObjectFormatter.PEM.Format(chainEntries[i].Certificate, false);
176156
}
177157
}
178158
}
179159

180-
storeInfo.Add(new SerializedStoreInfo() { FilePath = storePath+storeFileName, Contents = Encoding.ASCII.GetBytes(pemString) });
160+
storeInfo.Add(new SerializedStoreInfo() { FilePath = storePath + storeFileName, Contents = Encoding.ASCII.GetBytes(pemString) });
181161
if (!string.IsNullOrEmpty(SeparatePrivateKeyFilePath))
182162
storeInfo.Add(new SerializedStoreInfo() { FilePath = SeparatePrivateKeyFilePath, Contents = Encoding.ASCII.GetBytes(keyString) });
183163

@@ -215,18 +195,10 @@ private X509CertificateEntry[] GetCertificates(string certificates)
215195

216196
try
217197
{
218-
while (certificates.Contains(CertDelimBeg))
219-
{
220-
int certStart = certificates.IndexOf(CertDelimBeg);
221-
int certLength = certificates.IndexOf(CertDelimEnd) + CertDelimEnd.Length - certStart;
222-
string certificate = certificates.Substring(certStart, certLength);
223-
224-
CertificateConverter c2 = CertificateConverterFactory.FromPEM(Encoding.ASCII.GetBytes(certificate.Replace(CertDelimBeg, string.Empty).Replace(CertDelimEnd, string.Empty)));
225-
X509Certificate bcCert = c2.ToBouncyCastleCertificate();
226-
certificateEntries.Add(new X509CertificateEntry(bcCert));
227-
228-
certificates = certificates.Substring(certStart + certLength - 1);
229-
}
198+
IEnumerable<string> pemCertificates = PemUtilities.SplitCollection(certificates);
199+
certificateEntries.AddRange(pemCertificates.Select(cert =>
200+
new X509CertificateEntry(new X509Certificate(CryptographicObjectFormatter.DER.Format(cert))))
201+
);
230202
}
231203
catch (Exception ex)
232204
{

RemoteFile/InventoryBase.cs

Lines changed: 16 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -8,15 +8,18 @@
88
using System;
99
using System.Collections.Generic;
1010
using System.Linq;
11-
using System.Security.Cryptography.X509Certificates;
1211

1312
using Keyfactor.Orchestrators.Extensions;
1413
using Keyfactor.Orchestrators.Common.Enums;
1514
using Keyfactor.Logging;
1615
using Keyfactor.Extensions.Orchestrator.RemoteFile.Models;
16+
using Keyfactor.PKI.Extensions;
1717

1818
using Microsoft.Extensions.Logging;
1919
using Newtonsoft.Json;
20+
using Org.BouncyCastle.Pkcs;
21+
using System.Security.Cryptography.X509Certificates;
22+
using Keyfactor.PKI.X509;
2023

2124
namespace Keyfactor.Extensions.Orchestrator.RemoteFile
2225
{
@@ -38,32 +41,32 @@ public JobResult ProcessJob(InventoryJobConfiguration config, SubmitInventoryUpd
3841
SetJobProperties(config, config.CertificateStoreDetails, logger);
3942

4043
certificateStore = new RemoteCertificateStore(config.CertificateStoreDetails.ClientMachine, UserName, UserPassword, config.CertificateStoreDetails.StorePath, StorePassword, FileTransferProtocol, SSHPort, IncludePortInSPN);
41-
certificateStore.Initialize(SudoImpersonatedUser);
44+
certificateStore.Initialize(SudoImpersonatedUser, UseShellCommands);
4245
certificateStore.LoadCertificateStore(certificateStoreSerializer, true);
4346

44-
List<X509Certificate2Collection> collections = certificateStore.GetCertificateChains();
47+
List<X509CertificateEntryCollection> collection = certificateStore.GetCertificateChains();
4548

4649
logger.LogDebug($"Format returned certificates BEGIN");
47-
foreach (X509Certificate2Collection collection in collections)
50+
foreach (X509CertificateEntryCollection entry in collection)
4851
{
49-
if (collection.Count == 0)
52+
if (entry.CertificateChain?.Count > 1)
5053
continue;
5154

52-
X509Certificate2Ext issuedCertificate = (X509Certificate2Ext)collection[0];
55+
X509CertificateEntry issuedCertificate = entry.CertificateChain[0];
5356

5457
List<string> certChain = new List<string>();
55-
foreach (X509Certificate2 certificate in collection)
58+
foreach (X509CertificateEntry certificateEntry in entry.CertificateChain)
5659
{
57-
certChain.Add(Convert.ToBase64String(certificate.Export(X509ContentType.Cert)));
58-
logger.LogDebug(Convert.ToBase64String(certificate.Export(X509ContentType.Cert)));
60+
CertificateConverter converter = CertificateConverterFactory.FromBouncyCastleCertificate(certificateEntry.Certificate);
61+
certChain.Add(converter.ToPEM(false));
5962
}
60-
63+
6164
inventoryItems.Add(new CurrentInventoryItem()
6265
{
6366
ItemStatus = OrchestratorInventoryItemStatus.Unknown,
64-
Alias = string.IsNullOrEmpty(issuedCertificate.FriendlyNameExt) ? issuedCertificate.Thumbprint : issuedCertificate.FriendlyNameExt,
65-
PrivateKeyEntry = issuedCertificate.HasPrivateKey,
66-
UseChainLevel = collection.Count > 1,
67+
Alias = string.IsNullOrEmpty(entry.Alias) ? BouncyCastleX509Extensions.Thumbprint(issuedCertificate.Certificate) : entry.Alias,
68+
PrivateKeyEntry = entry.HasPrivateKey,
69+
UseChainLevel = entry.CertificateChain.Count > 1,
6770
Certificates = certChain.ToArray()
6871
});
6972
}

0 commit comments

Comments
 (0)