Skip to content

Commit fdc4cc4

Browse files
authored
Merge pull request #73 from Keyfactor/ab#55979
Ab#55979
2 parents 6fd6ed5 + 7d8c49e commit fdc4cc4

25 files changed

+443
-95
lines changed

CHANGELOG.md

+8
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,11 @@
1+
v2.10.0
2+
- Added support for Eliptical Curve (EC) private keys for RFPEM.
3+
- For Linux hosted certificate stores, added ability to inherit file permissions and ownership when creating new stores by modifying default behavior when config.json and certificate store permissions/ownership settings are left empty.
4+
- Added new optional custom field to store type definitions - IncludePortInSPN - which will set this option when creating remote Powershell connections.
5+
- Added new optional custom field to store type definitions - FileTransferProtocol - which will act as a store level override to the config.json setting.
6+
- Fixed documentation error in Discovery section
7+
- Added RemoveRootCertificate custom field to integration-manifest.json. This option was previously added in v2.8.0 but never added to the integration-manifest.json.
8+
19
v2.9.1
210
- Bug Fix: Use AES encryption when creating PKCS12 files instead of less secure defaults
311

README.md

+58-12
Large diffs are not rendered by default.

RemoteFile/ApplicationSettings.cs

+1-1
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ public enum FileTransferProtocolEnum
2525
Both
2626
}
2727

28-
private const string DEFAULT_LINUX_PERMISSION_SETTING = "600";
28+
private const string DEFAULT_LINUX_PERMISSION_SETTING = "";
2929
private const string DEFAULT_OWNER_SETTING = "";
3030
private const string DEFAULT_SUDO_IMPERSONATION_SETTING = "";
3131

RemoteFile/ImplementedStoreTypes/PEM/PEMCertificateStoreSerializer.cs

+118-54
Original file line numberDiff line numberDiff line change
@@ -21,21 +21,32 @@
2121

2222
using Microsoft.Extensions.Logging;
2323

24+
using Org.BouncyCastle.Math;
2425
using Org.BouncyCastle.Crypto;
2526
using Org.BouncyCastle.Pkcs;
2627
using Org.BouncyCastle.X509;
2728
using System.Security.Cryptography;
2829
using Org.BouncyCastle.OpenSsl;
30+
using Org.BouncyCastle.Crypto.Parameters;
31+
using Org.BouncyCastle.Asn1.X9;
2932

3033
namespace Keyfactor.Extensions.Orchestrator.RemoteFile.PEM
3134
{
3235
class PEMCertificateStoreSerializer : ICertificateStoreSerializer
3336
{
3437
string[] PrivateKeyDelimetersPkcs8 = new string[] { "-----BEGIN PRIVATE KEY-----", "-----BEGIN ENCRYPTED PRIVATE KEY-----" };
35-
string[] PrivateKeyDelimetersPkcs1 = new string[] { "-----BEGIN RSA PRIVATE KEY-----" };
38+
string[] PrivateKeyDelimetersRSA = new string[] { "-----BEGIN RSA PRIVATE KEY-----" };
39+
string[] PrivateKeyDelimetersEC = new string[] { "-----BEGIN EC PRIVATE KEY-----" };
3640
string CertDelimBeg = "-----BEGIN CERTIFICATE-----";
3741
string CertDelimEnd = "-----END CERTIFICATE-----";
3842

43+
private enum PrivateKeyTypeEnum
44+
{
45+
EC,
46+
RSA,
47+
PKCS8
48+
}
49+
3950
private bool IsTrustStore { get; set; }
4051
private bool IncludesChain { get; set; }
4152
private string SeparatePrivateKeyFilePath { get; set; }
@@ -68,11 +79,8 @@ public Pkcs12Store DeserializeRemoteCertificateStore(byte[] storeContentBytes, s
6879
}
6980
else
7081
{
71-
bool isRSAPrivateKey = false;
72-
AsymmetricKeyEntry keyEntry = GetPrivateKey(storeContents, storePassword ?? string.Empty, remoteHandler, out isRSAPrivateKey);
73-
74-
if (isRSAPrivateKey && !string.IsNullOrEmpty(storePassword))
75-
throw new RemoteFileException($"Certificate store with an RSA Private Key cannot contain a store password. Invalid store format not supported.");
82+
PrivateKeyTypeEnum privateKeyType;
83+
AsymmetricKeyEntry keyEntry = GetPrivateKey(storeContents, storePassword ?? string.Empty, remoteHandler, out privateKeyType);
7684

7785
store.SetKeyEntry(CertificateConverterFactory.FromBouncyCastleCertificate(certificates[0].Certificate).ToX509Certificate2().Thumbprint, keyEntry, certificates);
7886
}
@@ -112,16 +120,14 @@ public List<SerializedStoreInfo> SerializeRemoteCertificateStore(Pkcs12Store cer
112120
else
113121
{
114122
string storeContents = Encoding.ASCII.GetString(remoteHandler.DownloadCertificateFile(storePath + storeFileName));
115-
bool isRSAPrivateKey = false;
116-
try
117-
{
118-
GetPrivateKey(storeContents, storePassword, remoteHandler, out isRSAPrivateKey);
119-
}
120-
catch (RemoteFileException) { }
121123

122-
if (isRSAPrivateKey && !string.IsNullOrEmpty(storePassword))
123-
throw new RemoteFileException($"Certificate store with an RSA Private Key cannot contain a store password. Invalid store format not supported.");
124+
string begDelim;
125+
string privateKeyContents = String.IsNullOrEmpty(SeparatePrivateKeyFilePath) ? storeContents : Encoding.ASCII.GetString(remoteHandler.DownloadCertificateFile(SeparatePrivateKeyFilePath));
126+
PrivateKeyTypeEnum privateKeyType = GetPrivateKeyType(privateKeyContents, out begDelim);
124127

128+
if (!string.IsNullOrEmpty(storePassword) && privateKeyType != PrivateKeyTypeEnum.PKCS8)
129+
throw new RemoteFileException("Error retrieving private key. Certificate store password cannot have a non empty value if the private key is in PKCS#1 format (BEGIN [RSA|EC] PRIVATE KEY)");
130+
125131
bool keyEntryProcessed = false;
126132
foreach (string alias in certificateStore.Aliases)
127133
{
@@ -137,10 +143,16 @@ public List<SerializedStoreInfo> SerializeRemoteCertificateStore(Pkcs12Store cer
137143
CertificateConverter certConverter = CertificateConverterFactory.FromBouncyCastleCertificate(chainEntries[0].Certificate);
138144

139145
AsymmetricKeyParameter privateKey = certificateStore.GetKey(alias).Key;
140-
X509CertificateEntry[] certEntries = certificateStore.GetCertificateChain(alias);
141-
AsymmetricKeyParameter publicKey = certEntries[0].Certificate.GetPublicKey();
146+
AsymmetricKeyParameter publicKey = chainEntries[0].Certificate.GetPublicKey();
142147

143-
if (isRSAPrivateKey)
148+
if (privateKeyType == PrivateKeyTypeEnum.PKCS8)
149+
{
150+
PrivateKeyConverter keyConverter = PrivateKeyConverterFactory.FromBCKeyPair(privateKey, publicKey, false);
151+
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
144156
{
145157
TextWriter textWriter = new StringWriter();
146158
PemWriter pemWriter = new PemWriter(textWriter);
@@ -149,13 +161,6 @@ public List<SerializedStoreInfo> SerializeRemoteCertificateStore(Pkcs12Store cer
149161

150162
keyString = textWriter.ToString();
151163
}
152-
else
153-
{
154-
PrivateKeyConverter keyConverter = PrivateKeyConverterFactory.FromBCKeyPair(privateKey, publicKey, false);
155-
156-
byte[] privateKeyBytes = string.IsNullOrEmpty(storePassword) ? keyConverter.ToPkcs8BlobUnencrypted() : keyConverter.ToPkcs8Blob(storePassword);
157-
keyString = PemUtilities.DERToPEM(privateKeyBytes, string.IsNullOrEmpty(storePassword) ? PemUtilities.PemObjectType.PrivateKey : PemUtilities.PemObjectType.EncryptedPrivateKey);
158-
}
159164

160165
pemString = certConverter.ToPEM(true);
161166
if (string.IsNullOrEmpty(SeparatePrivateKeyFilePath))
@@ -230,61 +235,120 @@ private X509CertificateEntry[] GetCertificates(string certificates)
230235
return certificateEntries.ToArray();
231236
}
232237

233-
private AsymmetricKeyEntry GetPrivateKey(string storeContents, string storePassword, IRemoteHandler remoteHandler, out bool isRSA)
238+
private AsymmetricKeyEntry GetPrivateKey(string storeContents, string storePassword, IRemoteHandler remoteHandler, out PrivateKeyTypeEnum privateKeyType)
234239
{
235240
logger.MethodEntry(LogLevel.Debug);
236241

242+
AsymmetricKeyEntry keyEntry = null;
243+
237244
if (!String.IsNullOrEmpty(SeparatePrivateKeyFilePath))
238245
{
239246
storeContents = Encoding.ASCII.GetString(remoteHandler.DownloadCertificateFile(SeparatePrivateKeyFilePath));
240247
}
241248

242-
isRSA = false;
243-
foreach (string begDelim in PrivateKeyDelimetersPkcs1)
249+
string begDelim = string.Empty;
250+
privateKeyType = GetPrivateKeyType(storeContents, out begDelim);
251+
252+
string privateKey = string.Empty;
253+
string endDelim = begDelim.Replace("BEGIN", "END");
254+
255+
int keyStart = storeContents.IndexOf(begDelim);
256+
if (keyStart == -1)
257+
throw new RemoteFileException("Invalid private key: No beginning private key delimiter found.");
258+
259+
int keyLength = storeContents.IndexOf(endDelim) + endDelim.Length - keyStart;
260+
if (keyLength == -1)
261+
throw new RemoteFileException("Invalid private key: No ending private key delimiter found.");
262+
263+
privateKey = storeContents.Substring(keyStart, keyLength).Replace(begDelim, string.Empty).Replace(endDelim, string.Empty);
264+
265+
if (string.IsNullOrEmpty(privateKey))
266+
throw new RemoteFileException("Invalid private key: No private key or invalid private key format found.");
267+
268+
PrivateKeyConverter c = null;
269+
int bytesRead;
270+
switch (privateKeyType)
271+
{
272+
case PrivateKeyTypeEnum.PKCS8:
273+
c = PrivateKeyConverterFactory.FromPkcs8Blob(Convert.FromBase64String(privateKey), storePassword);
274+
keyEntry = new AsymmetricKeyEntry(c.ToBCPrivateKey());
275+
break;
276+
case PrivateKeyTypeEnum.RSA:
277+
RSA rsa = RSA.Create();
278+
rsa.ImportRSAPrivateKey(Convert.FromBase64String(privateKey), out bytesRead);
279+
c = PrivateKeyConverterFactory.FromNetPrivateKey(rsa, false);
280+
keyEntry = new AsymmetricKeyEntry(c.ToBCPrivateKey());
281+
break;
282+
case PrivateKeyTypeEnum.EC:
283+
ECDiffieHellman ec = ECDiffieHellman.Create();
284+
keyEntry = new AsymmetricKeyEntry(this.ToBCPrivateKey(ec));
285+
break;
286+
}
287+
288+
logger.MethodExit(LogLevel.Debug);
289+
290+
return keyEntry;
291+
}
292+
293+
private PrivateKeyTypeEnum GetPrivateKeyType(string storeContents, out string privateKeyBegDelim)
294+
{
295+
foreach (string begDelim in PrivateKeyDelimetersPkcs8)
296+
{
297+
if (string.IsNullOrEmpty(storeContents) || storeContents.Contains(begDelim))
298+
{
299+
privateKeyBegDelim = begDelim;
300+
return PrivateKeyTypeEnum.PKCS8;
301+
}
302+
}
303+
304+
foreach (string begDelim in PrivateKeyDelimetersRSA)
244305
{
245306
if (storeContents.Contains(begDelim))
246307
{
247-
isRSA = true;
248-
break;
308+
privateKeyBegDelim = begDelim;
309+
return PrivateKeyTypeEnum.RSA;
249310
}
250311
}
251312

252-
string privateKey = string.Empty;
253-
foreach (string begDelim in isRSA ? PrivateKeyDelimetersPkcs1 : PrivateKeyDelimetersPkcs8)
313+
foreach (string begDelim in PrivateKeyDelimetersEC)
254314
{
255-
string endDelim = begDelim.Replace("BEGIN", "END");
315+
if (storeContents.Contains(begDelim))
316+
{
317+
privateKeyBegDelim = begDelim;
318+
return PrivateKeyTypeEnum.EC;
319+
}
320+
}
256321

257-
int keyStart = storeContents.IndexOf(begDelim);
258-
if (keyStart == -1)
259-
continue;
260-
int keyLength = storeContents.IndexOf(endDelim) + endDelim.Length - keyStart;
261-
if (keyLength == -1)
262-
throw new RemoteFileException("Invalid private key: No ending private key delimiter found.");
322+
throw new RemoteFileException("Invalid or unsupported Private Key format.");
323+
}
263324

264-
privateKey = storeContents.Substring(keyStart, keyLength).Replace(begDelim, string.Empty).Replace(endDelim, string.Empty);
325+
private AsymmetricKeyParameter ToBCPrivateKey(ECDiffieHellman ecdh)
326+
{
327+
// Export the key as ECParameters
328+
ECParameters parameters = ecdh.ExportParameters(true);
265329

266-
break;
267-
}
330+
// Convert the parameters to a BouncyCastle ECCurve
331+
var curve2 = ECNamedCurveTable.GetByName(parameters.Curve.Oid.FriendlyName);
332+
var curve = ECNamedCurveTable.GetByOid(new Org.BouncyCastle.Asn1.DerObjectIdentifier(parameters.Curve.Oid.Value));
333+
if (curve == null)
334+
throw new RemoteFileException("Error converting to BouncyCastle private key - Unsupported curve");
268335

269-
if (string.IsNullOrEmpty(privateKey))
270-
throw new RemoteFileException("Invalid private key: No private key or invalid private key format found.");
336+
// Convert the parameters to BigInteger
337+
var q = curve.Curve.CreatePoint(
338+
new BigInteger(1, parameters.Q.X),
339+
new BigInteger(1, parameters.Q.Y));
271340

272-
PrivateKeyConverter c;
273-
if (isRSA)
341+
if (parameters.D != null)
274342
{
275-
RSA rsa = RSA.Create();
276-
int bytesRead;
277-
rsa.ImportRSAPrivateKey(Convert.FromBase64String(privateKey), out bytesRead);
278-
c = PrivateKeyConverterFactory.FromNetPrivateKey(rsa, false);
343+
// Create private key parameter
344+
return new ECPrivateKeyParameters(
345+
new BigInteger(1, parameters.D),
346+
new ECDomainParameters(curve));
279347
}
280348
else
281349
{
282-
c = PrivateKeyConverterFactory.FromPkcs8Blob(Convert.FromBase64String(privateKey), storePassword);
350+
throw new RemoteFileException("Error converting to BouncyCastle private key - Invalid parameter.");
283351
}
284-
285-
logger.MethodExit(LogLevel.Debug);
286-
287-
return new AsymmetricKeyEntry(c.ToBCPrivateKey());
288352
}
289353
}
290354
}

RemoteFile/InventoryBase.cs

+10-1
Original file line numberDiff line numberDiff line change
@@ -51,8 +51,17 @@ public JobResult ProcessJob(InventoryJobConfiguration config, SubmitInventoryUpd
5151
string sudoImpersonatedUser = properties.SudoImpersonatedUser == null || string.IsNullOrEmpty(properties.SudoImpersonatedUser.Value) ?
5252
ApplicationSettings.DefaultSudoImpersonatedUser :
5353
properties.SudoImpersonatedUser.Value;
54+
bool includePortInSPN = properties.IncludePortInSPN == null || string.IsNullOrEmpty(properties.IncludePortInSPN.Value) ?
55+
false :
56+
Convert.ToBoolean(properties.IncludePortInSPN.Value);
5457

55-
certificateStore = new RemoteCertificateStore(config.CertificateStoreDetails.ClientMachine, userName, userPassword, config.CertificateStoreDetails.StorePath, storePassword, config.JobProperties);
58+
ApplicationSettings.FileTransferProtocolEnum fileTransferProtocol = ApplicationSettings.FileTransferProtocol;
59+
if (properties.FileTransferProtocol != null && !string.IsNullOrEmpty(properties.FileTransferProtocol.Value))
60+
{
61+
Enum.TryParse(properties.FileTransferProtocol.Value, out fileTransferProtocol);
62+
}
63+
64+
certificateStore = new RemoteCertificateStore(config.CertificateStoreDetails.ClientMachine, userName, userPassword, config.CertificateStoreDetails.StorePath, storePassword, fileTransferProtocol, includePortInSPN);
5665
certificateStore.Initialize(sudoImpersonatedUser);
5766
certificateStore.LoadCertificateStore(certificateStoreSerializer, true);
5867

RemoteFile/ManagementBase.cs

+11-2
Original file line numberDiff line numberDiff line change
@@ -53,8 +53,17 @@ public JobResult ProcessJob(ManagementJobConfiguration config)
5353
bool removeRootCertificate = properties.RemoveRootCertificate == null || string.IsNullOrEmpty(properties.RemoveRootCertificate.Value) ?
5454
false :
5555
Convert.ToBoolean(properties.RemoveRootCertificate.Value);
56+
bool includePortInSPN = properties.IncludePortInSPN == null || string.IsNullOrEmpty(properties.IncludePortInSPN.Value) ?
57+
false :
58+
Convert.ToBoolean(properties.IncludePortInSPN.Value);
59+
60+
ApplicationSettings.FileTransferProtocolEnum fileTransferProtocol = ApplicationSettings.FileTransferProtocol;
61+
if (properties.FileTransferProtocol != null && !string.IsNullOrEmpty(properties.FileTransferProtocol.Value))
62+
{
63+
Enum.TryParse(properties.FileTransferProtocol.Value, out fileTransferProtocol);
64+
}
5665

57-
certificateStore = new RemoteCertificateStore(config.CertificateStoreDetails.ClientMachine, userName, userPassword, config.CertificateStoreDetails.StorePath, storePassword, config.JobProperties);
66+
certificateStore = new RemoteCertificateStore(config.CertificateStoreDetails.ClientMachine, userName, userPassword, config.CertificateStoreDetails.StorePath, storePassword, fileTransferProtocol, includePortInSPN);
5867
certificateStore.Initialize(sudoImpersonatedUser);
5968

6069
PathFile storePathFile = RemoteCertificateStore.SplitStorePathFile(config.CertificateStoreDetails.StorePath);
@@ -135,7 +144,7 @@ private void CreateStore(ICertificateStoreSerializer certificateStoreSerializer,
135144
ApplicationSettings.DefaultOwnerOnStoreCreation :
136145
properties.LinuxFileOwnerOnStoreCreation.Value;
137146

138-
certificateStore.CreateCertificateStore(certificateStoreSerializer, config.CertificateStoreDetails.StorePath, linuxFilePermissions, string.IsNullOrEmpty(linuxFileOwner) ? config.ServerUsername : linuxFileOwner);
147+
certificateStore.CreateCertificateStore(certificateStoreSerializer, config.CertificateStoreDetails.StorePath, linuxFilePermissions, linuxFileOwner);
139148
}
140149
}
141150
}

0 commit comments

Comments
 (0)