From 6b28cf3a606646ec64210e761e152d19d4118f96 Mon Sep 17 00:00:00 2001 From: leefine02 Date: Tue, 18 Jun 2024 20:16:13 +0000 Subject: [PATCH 01/20] ab#59979 --- RemoteFile/RemoteHandlers/SSHHandler.cs | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/RemoteFile/RemoteHandlers/SSHHandler.cs b/RemoteFile/RemoteHandlers/SSHHandler.cs index 38854b2..dd08404 100644 --- a/RemoteFile/RemoteHandlers/SSHHandler.cs +++ b/RemoteFile/RemoteHandlers/SSHHandler.cs @@ -47,7 +47,7 @@ internal SSHHandler(string server, string serverLogin, string serverPassword, bo try { - using (MemoryStream ms = new MemoryStream(Encoding.ASCII.GetBytes(FormatRSAPrivateKey(serverPassword)))) + using (MemoryStream ms = new MemoryStream(Encoding.ASCII.GetBytes(FormatPrivateKey(serverPassword)))) { privateKeyFile = new PrivateKeyFile(ms); } @@ -400,12 +400,14 @@ private void SplitStorePathFile(string pathFileName, out string path, out string _logger.MethodEntry(LogLevel.Debug); } - private string FormatRSAPrivateKey(string privateKey) + private string FormatPrivateKey(string privateKey) { _logger.MethodEntry(LogLevel.Debug); _logger.MethodExit(LogLevel.Debug); + + String keyType = privateKey.Contains("OPENSSH PRIVATE KEY") ? "OPENSSH" : "RSA"; - return privateKey.Replace(" RSA PRIVATE ", "^^^").Replace(" ", System.Environment.NewLine).Replace("^^^", " RSA PRIVATE ") + System.Environment.NewLine; + return privateKey.Replace($" {keyType} PRIVATE ", "^^^").Replace(" ", System.Environment.NewLine).Replace("^^^", $" {keyType} PRIVATE ") + System.Environment.NewLine; } private string ConvertToPKCS1(string privateKey) From e858bec71cccc3d4c3779788016a1573ad482ddf Mon Sep 17 00:00:00 2001 From: leefine02 Date: Wed, 19 Jun 2024 12:47:29 +0000 Subject: [PATCH 02/20] ab#59979 --- readme_source.md | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/readme_source.md b/readme_source.md index eca1ab0..ccb9ee9 100644 --- a/readme_source.md +++ b/readme_source.md @@ -118,7 +118,10 @@ The version number of a the Remote File Orchestrator Extension can be verified b 2. When orchestrating management of local or external certificate stores, the Remote File Orchestrator Extension makes use of SFTP and/or SCP to transfer files to and from the orchestrated server. SFTP/SCP cannot make use of sudo, so all folders containing certificate stores will need to allow SFTP/SCP file transfer for the user assigned to the certificate store/discovery job. If this is not possible, set the values in the config.json apprpriately to use an alternative upload/download folder that does allow SFTP/SCP file transfer. If the certificate store/discovery job is configured for local (agent) access, the account running the Keyfactor Universal Orchestrator service must have access to read/write to the certificate store location, OR the config.json file must be set up to use the alternative upload/download file. -3. SSH Key Authentication: When creating a Keyfactor certificate store for the remote file orchestrator extension, you may supply either a user id and password for the certificate store credentials (directly or through one of Keyfactor Command's PAM integrations), or supply a user id and SSH private key. Both PKCS#1 (BEGIN RSA PRIVATE KEY) and PKCS#8 (BEGIN PRIVATE KEY) formats are supported for the SSH private key. If using the normal Keyfactor Command credentials dialog without PAM integration, just copy and paste the full SSH private key into the Password textbox. SSH Key Authentication is not available when running locally as an agent. +3. SSH Key Authentication: When creating a Keyfactor certificate store for the remote file orchestrator extension, you may supply either a user id and password for the certificate store credentials (directly or through one of Keyfactor Command's PAM integrations), or supply a user id and SSH private key. If using the normal Keyfactor Command credentials dialog without PAM integration, just copy and paste the full SSH private key into the Password textbox. SSH Key Authentication is not available when running locally as an agent. The following private key formats are supported: +- PKCS#1 (BEGIN RSA PRIVATE KEY) +- PKCS#8 (BEGIN PRIVATE KEY) +- ECDSA OPENSSH (BEGIN OPENSSH PRIVATE KEY) Please reference [Configuration File Setup](#configuration-file-setup) for more information on setting up the config.json file and [Certificate Stores and Discovery Jobs](#certificate-stores-and-discovery-jobs) for more information on the items above. From b8bcb35c59088f038f443138f0a011f9c8df2091 Mon Sep 17 00:00:00 2001 From: Keyfactor Date: Wed, 19 Jun 2024 12:48:02 +0000 Subject: [PATCH 03/20] Update generated README --- README.md | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index ca0af93..5be2b41 100644 --- a/README.md +++ b/README.md @@ -219,7 +219,10 @@ The version number of a the Remote File Orchestrator Extension can be verified b 2. When orchestrating management of local or external certificate stores, the Remote File Orchestrator Extension makes use of SFTP and/or SCP to transfer files to and from the orchestrated server. SFTP/SCP cannot make use of sudo, so all folders containing certificate stores will need to allow SFTP/SCP file transfer for the user assigned to the certificate store/discovery job. If this is not possible, set the values in the config.json apprpriately to use an alternative upload/download folder that does allow SFTP/SCP file transfer. If the certificate store/discovery job is configured for local (agent) access, the account running the Keyfactor Universal Orchestrator service must have access to read/write to the certificate store location, OR the config.json file must be set up to use the alternative upload/download file. -3. SSH Key Authentication: When creating a Keyfactor certificate store for the remote file orchestrator extension, you may supply either a user id and password for the certificate store credentials (directly or through one of Keyfactor Command's PAM integrations), or supply a user id and SSH private key. Both PKCS#1 (BEGIN RSA PRIVATE KEY) and PKCS#8 (BEGIN PRIVATE KEY) formats are supported for the SSH private key. If using the normal Keyfactor Command credentials dialog without PAM integration, just copy and paste the full SSH private key into the Password textbox. SSH Key Authentication is not available when running locally as an agent. +3. SSH Key Authentication: When creating a Keyfactor certificate store for the remote file orchestrator extension, you may supply either a user id and password for the certificate store credentials (directly or through one of Keyfactor Command's PAM integrations), or supply a user id and SSH private key. If using the normal Keyfactor Command credentials dialog without PAM integration, just copy and paste the full SSH private key into the Password textbox. SSH Key Authentication is not available when running locally as an agent. The following private key formats are supported: +- PKCS#1 (BEGIN RSA PRIVATE KEY) +- PKCS#8 (BEGIN PRIVATE KEY) +- ECDSA OPENSSH (BEGIN OPENSSH PRIVATE KEY) Please reference [Configuration File Setup](#configuration-file-setup) for more information on setting up the config.json file and [Certificate Stores and Discovery Jobs](#certificate-stores-and-discovery-jobs) for more information on the items above. From 628f2499b604bdfc70a104750e2feb900ca06284 Mon Sep 17 00:00:00 2001 From: leefine02 Date: Wed, 19 Jun 2024 14:52:13 +0000 Subject: [PATCH 04/20] ab#59979 --- RemoteFile/ManagementBase.cs | 2 +- RemoteFile/RemoteCertificateStore.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/RemoteFile/ManagementBase.cs b/RemoteFile/ManagementBase.cs index a6c213c..524873f 100644 --- a/RemoteFile/ManagementBase.cs +++ b/RemoteFile/ManagementBase.cs @@ -73,7 +73,7 @@ public JobResult ProcessJob(ManagementJobConfiguration config) throw new RemoteFileException($"Certificate store {config.CertificateStoreDetails.StorePath} does not exist on server {config.CertificateStoreDetails.ClientMachine}."); } certificateStore.LoadCertificateStore(certificateStoreSerializer, config.CertificateStoreDetails.Properties, false); - certificateStore.AddCertificate((config.JobCertificate.Alias ?? new X509Certificate2(Convert.FromBase64String(config.JobCertificate.Contents), config.JobCertificate.PrivateKeyPassword).Thumbprint), config.JobCertificate.Contents, config.Overwrite, config.JobCertificate.PrivateKeyPassword); + certificateStore.AddCertificate((config.JobCertificate.Alias ?? new X509Certificate2(Convert.FromBase64String(config.JobCertificate.Contents), config.JobCertificate.PrivateKeyPassword, X509KeyStorageFlags.EphemeralKeySet).Thumbprint), config.JobCertificate.Contents, config.Overwrite, config.JobCertificate.PrivateKeyPassword); certificateStore.SaveCertificateStore(certificateStoreSerializer.SerializeRemoteCertificateStore(certificateStore.GetCertificateStore(), storePathFile.Path, storePathFile.File, storePassword, certificateStore.RemoteHandler)); logger.LogDebug($"END add Operation for {config.CertificateStoreDetails.StorePath} on {config.CertificateStoreDetails.ClientMachine}."); diff --git a/RemoteFile/RemoteCertificateStore.cs b/RemoteFile/RemoteCertificateStore.cs index 3e23edd..104b671 100644 --- a/RemoteFile/RemoteCertificateStore.cs +++ b/RemoteFile/RemoteCertificateStore.cs @@ -248,7 +248,7 @@ internal void AddCertificate(string alias, string certificateEntry, bool overwri Pkcs12Store newEntry = storeBuilder.Build(); - X509Certificate2 cert = new X509Certificate2(newCertBytes, pfxPassword, X509KeyStorageFlags.Exportable); + X509Certificate2 cert = new X509Certificate2(newCertBytes, pfxPassword, X509KeyStorageFlags.Exportable | X509KeyStorageFlags.EphemeralKeySet); byte[] binaryCert = cert.Export(X509ContentType.Pkcs12, pfxPassword); using (MemoryStream ms = new MemoryStream(string.IsNullOrEmpty(pfxPassword) ? binaryCert : newCertBytes)) From 625b7ea56c1599865caaa5cc9d21f4160f6ccd8f Mon Sep 17 00:00:00 2001 From: leefine02 Date: Wed, 19 Jun 2024 20:18:01 +0000 Subject: [PATCH 05/20] ab#59979 --- .../JKS/JKSCertificateStoreSerializer.cs | 120 +++++++++++------- readme_source.md | 2 +- 2 files changed, 76 insertions(+), 46 deletions(-) diff --git a/RemoteFile/ImplementedStoreTypes/JKS/JKSCertificateStoreSerializer.cs b/RemoteFile/ImplementedStoreTypes/JKS/JKSCertificateStoreSerializer.cs index c285ea9..164ecc1 100644 --- a/RemoteFile/ImplementedStoreTypes/JKS/JKSCertificateStoreSerializer.cs +++ b/RemoteFile/ImplementedStoreTypes/JKS/JKSCertificateStoreSerializer.cs @@ -9,6 +9,7 @@ using System.Collections.Generic; using Keyfactor.Extensions.Orchestrator.RemoteFile.RemoteHandlers; using Keyfactor.Extensions.Orchestrator.RemoteFile.Models; +using Keyfactor.Extensions.Orchestrator.RemoteFile.PKCS12; using Keyfactor.Logging; @@ -30,6 +31,8 @@ public JKSCertificateStoreSerializer(string storeProperties) logger = LogHandler.GetClassLogger(this.GetType()); } + private bool IsTypeJKS { get; set; } + public Pkcs12Store DeserializeRemoteCertificateStore(byte[] storeContents, string storePath, string storePassword, IRemoteHandler remoteHandler, bool isInventory) { logger.MethodEntry(LogLevel.Debug); @@ -38,42 +41,58 @@ public Pkcs12Store DeserializeRemoteCertificateStore(byte[] storeContents, strin Pkcs12Store pkcs12Store = storeBuilder.Build(); Pkcs12Store pkcs12StoreNew = storeBuilder.Build(); - JksStore jksStore = new JksStore(); - using (MemoryStream ms = new MemoryStream(storeContents)) { - jksStore.Load(ms, string.IsNullOrEmpty(storePassword) ? new char[0] : storePassword.ToCharArray()); + IsTypeJKS = new JksStore().Probe(ms); } - foreach (string alias in jksStore.Aliases) + if (IsTypeJKS) { - if (jksStore.IsKeyEntry(alias)) + logger.LogDebug("Store is of type JKS"); + JksStore jksStore = new JksStore(); + + using (MemoryStream ms = new MemoryStream(storeContents)) { - AsymmetricKeyParameter keyParam = jksStore.GetKey(alias, string.IsNullOrEmpty(storePassword) ? new char[0] : storePassword.ToCharArray()); - AsymmetricKeyEntry keyEntry = new AsymmetricKeyEntry(keyParam); + ms.Position = 0; + jksStore.Load(ms, string.IsNullOrEmpty(storePassword) ? new char[0] : storePassword.ToCharArray()); + } - X509Certificate[] certificateChain = jksStore.GetCertificateChain(alias); - List certificateChainEntries = new List(); - foreach (X509Certificate certificate in certificateChain) + foreach (string alias in jksStore.Aliases) + { + if (jksStore.IsKeyEntry(alias)) { - certificateChainEntries.Add(new X509CertificateEntry(certificate)); - } + AsymmetricKeyParameter keyParam = jksStore.GetKey(alias, string.IsNullOrEmpty(storePassword) ? new char[0] : storePassword.ToCharArray()); + AsymmetricKeyEntry keyEntry = new AsymmetricKeyEntry(keyParam); - pkcs12Store.SetKeyEntry(alias, keyEntry, certificateChainEntries.ToArray()); - } - else - { - pkcs12Store.SetCertificateEntry(alias, new X509CertificateEntry(jksStore.GetCertificate(alias))); + X509Certificate[] certificateChain = jksStore.GetCertificateChain(alias); + List certificateChainEntries = new List(); + foreach (X509Certificate certificate in certificateChain) + { + certificateChainEntries.Add(new X509CertificateEntry(certificate)); + } + + pkcs12Store.SetKeyEntry(alias, keyEntry, certificateChainEntries.ToArray()); + } + else + { + pkcs12Store.SetCertificateEntry(alias, new X509CertificateEntry(jksStore.GetCertificate(alias))); + } } - } - // 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 - // when processing store. - MemoryStream ms2 = new MemoryStream(); - pkcs12Store.Save(ms2, string.IsNullOrEmpty(storePassword) ? new char[0] : storePassword.ToCharArray(), new Org.BouncyCastle.Security.SecureRandom()); - ms2.Position = 0; + // 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 + // when processing store. + MemoryStream ms2 = new MemoryStream(); + pkcs12Store.Save(ms2, string.IsNullOrEmpty(storePassword) ? new char[0] : storePassword.ToCharArray(), new Org.BouncyCastle.Security.SecureRandom()); + ms2.Position = 0; - pkcs12StoreNew.Load(ms2, string.IsNullOrEmpty(storePassword) ? new char[0] : storePassword.ToCharArray()); + pkcs12StoreNew.Load(ms2, string.IsNullOrEmpty(storePassword) ? new char[0] : storePassword.ToCharArray()); + } + else + { + logger.LogDebug("Store is of type PKCS12"); + PKCS12CertificateStoreSerializer pkcs12Serializer = new PKCS12CertificateStoreSerializer(string.Empty); + pkcs12StoreNew = pkcs12Serializer.DeserializeRemoteCertificateStore(storeContents, storePath, storePassword, remoteHandler, isInventory); + } logger.MethodExit(LogLevel.Debug); return pkcs12StoreNew; @@ -83,39 +102,50 @@ public List SerializeRemoteCertificateStore(Pkcs12Store cer { logger.MethodEntry(LogLevel.Debug); - JksStore jksStore = new JksStore(); + List storeInfo = new List(); - foreach (string alias in certificateStore.Aliases) + if (IsTypeJKS) { - if (certificateStore.IsKeyEntry(alias)) + JksStore jksStore = new JksStore(); + + foreach (string alias in certificateStore.Aliases) { - AsymmetricKeyEntry keyEntry = certificateStore.GetKey(alias); - X509CertificateEntry[] certificateChain = certificateStore.GetCertificateChain(alias); + if (certificateStore.IsKeyEntry(alias)) + { + AsymmetricKeyEntry keyEntry = certificateStore.GetKey(alias); + X509CertificateEntry[] certificateChain = certificateStore.GetCertificateChain(alias); - List certificates = new List(); - foreach (X509CertificateEntry certificateEntry in certificateChain) + List certificates = new List(); + foreach (X509CertificateEntry certificateEntry in certificateChain) + { + certificates.Add(certificateEntry.Certificate); + } + + jksStore.SetKeyEntry(alias, keyEntry.Key, string.IsNullOrEmpty(storePassword) ? new char[0] : storePassword.ToCharArray(), certificates.ToArray()); + } + else { - certificates.Add(certificateEntry.Certificate); + jksStore.SetCertificateEntry(alias, certificateStore.GetCertificate(alias).Certificate); } - - jksStore.SetKeyEntry(alias, keyEntry.Key, string.IsNullOrEmpty(storePassword) ? new char[0] : storePassword.ToCharArray(), certificates.ToArray()); } - else + + using (MemoryStream outStream = new MemoryStream()) { - jksStore.SetCertificateEntry(alias, certificateStore.GetCertificate(alias).Certificate); + jksStore.Save(outStream, string.IsNullOrEmpty(storePassword) ? new char[0] : storePassword.ToCharArray()); + + storeInfo.Add(new SerializedStoreInfo() { FilePath = storePath + storeFileName, Contents = outStream.ToArray() }); + + logger.MethodExit(LogLevel.Debug); + return storeInfo; } } - - using (MemoryStream outStream = new MemoryStream()) + else { - jksStore.Save(outStream, string.IsNullOrEmpty(storePassword) ? new char[0] : storePassword.ToCharArray()); - - List storeInfo = new List(); - storeInfo.Add(new SerializedStoreInfo() { FilePath = storePath + storeFileName, Contents = outStream.ToArray() }); - - logger.MethodExit(LogLevel.Debug); - return storeInfo; + PKCS12CertificateStoreSerializer pkcs12Serializer = new PKCS12CertificateStoreSerializer(string.Empty); + storeInfo = pkcs12Serializer.SerializeRemoteCertificateStore(certificateStore, storePath, storeFileName, storePassword, remoteHandler); } + + return storeInfo; } public string GetPrivateKeyPath() diff --git a/readme_source.md b/readme_source.md index ccb9ee9..a84bf82 100644 --- a/readme_source.md +++ b/readme_source.md @@ -17,7 +17,7 @@ Use cases supported:
RFJKS -The RFJKS store type can be used to manage java keystores of type JKS. **PLEASE NOTE:** Java keystores of type PKCS12 **_cannot_** be managed by the RFJKS type. You **_must_** use RFPkcs12. +The RFJKS store type can be used to manage java keystores of types JKS or PKCS12. If creating a new java keystore and adding a certificate all via Keyfactor Command, the created java keystore will be of type PKCS12, as java keystores of type JKS have been deprecated as of JDK 9. Use cases supported: 1. One-to-many trust entries - A trust entry is considered single certificate without a private key in a certificate store. Each trust entry is identified with a custom alias. From 988121df1cb6eb07ad783cb8b2a484d04a0d9d63 Mon Sep 17 00:00:00 2001 From: Keyfactor Date: Wed, 19 Jun 2024 20:18:37 +0000 Subject: [PATCH 06/20] Update generated README --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 5be2b41..be1d336 100644 --- a/README.md +++ b/README.md @@ -118,7 +118,7 @@ Use cases supported:
RFJKS -The RFJKS store type can be used to manage java keystores of type JKS. **PLEASE NOTE:** Java keystores of type PKCS12 **_cannot_** be managed by the RFJKS type. You **_must_** use RFPkcs12. +The RFJKS store type can be used to manage java keystores of types JKS or PKCS12. If creating a new java keystore and adding a certificate all via Keyfactor Command, the created java keystore will be of type PKCS12, as java keystores of type JKS have been deprecated as of JDK 9. Use cases supported: 1. One-to-many trust entries - A trust entry is considered single certificate without a private key in a certificate store. Each trust entry is identified with a custom alias. From 348532445b6e31d2ac36e9ad657a006d7a773c93 Mon Sep 17 00:00:00 2001 From: leefine02 Date: Mon, 24 Jun 2024 14:36:42 +0000 Subject: [PATCH 07/20] ab#59979 --- RemoteFile/ApplicationSettings.cs | 1 + RemoteFile/Discovery.cs | 2 +- .../ImplementedStoreTypes/DER/Reenrollment.cs | 24 +++ .../ImplementedStoreTypes/JKS/Reenrollment.cs | 24 +++ .../ImplementedStoreTypes/KDB/Reenrollment.cs | 24 +++ .../OraWlt/Reenrollment.cs | 25 ++++ .../ImplementedStoreTypes/PEM/Reenrollment.cs | 25 ++++ .../PKCS12/Reenrollment.cs | 24 +++ RemoteFile/InventoryBase.cs | 2 +- RemoteFile/ManagementBase.cs | 9 +- RemoteFile/ReenrollmentBase.cs | 138 ++++++++++++++++++ RemoteFile/config.json | 3 +- 12 files changed, 290 insertions(+), 11 deletions(-) create mode 100644 RemoteFile/ImplementedStoreTypes/DER/Reenrollment.cs create mode 100644 RemoteFile/ImplementedStoreTypes/JKS/Reenrollment.cs create mode 100644 RemoteFile/ImplementedStoreTypes/KDB/Reenrollment.cs create mode 100644 RemoteFile/ImplementedStoreTypes/OraWlt/Reenrollment.cs create mode 100644 RemoteFile/ImplementedStoreTypes/PEM/Reenrollment.cs create mode 100644 RemoteFile/ImplementedStoreTypes/PKCS12/Reenrollment.cs create mode 100644 RemoteFile/ReenrollmentBase.cs diff --git a/RemoteFile/ApplicationSettings.cs b/RemoteFile/ApplicationSettings.cs index 2b65a87..790ad4d 100644 --- a/RemoteFile/ApplicationSettings.cs +++ b/RemoteFile/ApplicationSettings.cs @@ -38,6 +38,7 @@ public enum FileTransferProtocolEnum public static string DefaultLinuxPermissionsOnStoreCreation { get { return configuration.ContainsKey("DefaultLinuxPermissionsOnStoreCreation") ? configuration["DefaultLinuxPermissionsOnStoreCreation"] : DEFAULT_LINUX_PERMISSION_SETTING; } } public static string DefaultOwnerOnStoreCreation { get { return configuration.ContainsKey("DefaultOwnerOnStoreCreation") ? configuration["DefaultOwnerOnStoreCreation"] : DEFAULT_OWNER_SETTING; } } public static string DefaultSudoImpersonatedUser { get { return configuration.ContainsKey("DefaultSudoImpersonatedUser") ? configuration["DefaultSudoImpersonatedUser"] : DEFAULT_SUDO_IMPERSONATION_SETTING; } } + public static bool CreateCSROnDevice { get { return configuration.ContainsKey("CreateCSROnDevice") ? configuration["CreateCSROnDevice"]?.ToUpper() == "Y" : false; } } public static FileTransferProtocolEnum FileTransferProtocol { get diff --git a/RemoteFile/Discovery.cs b/RemoteFile/Discovery.cs index 42df0c7..42e78e7 100644 --- a/RemoteFile/Discovery.cs +++ b/RemoteFile/Discovery.cs @@ -22,7 +22,7 @@ namespace Keyfactor.Extensions.Orchestrator.RemoteFile public class Discovery: IDiscoveryJobExtension { public IPAMSecretResolver _resolver; - public string ExtensionName => ""; + public string ExtensionName => "Keyfactor.Extensions.Orchestrator.RemoteFile.Discovery"; public Discovery(IPAMSecretResolver resolver) { diff --git a/RemoteFile/ImplementedStoreTypes/DER/Reenrollment.cs b/RemoteFile/ImplementedStoreTypes/DER/Reenrollment.cs new file mode 100644 index 0000000..6659e5d --- /dev/null +++ b/RemoteFile/ImplementedStoreTypes/DER/Reenrollment.cs @@ -0,0 +1,24 @@ +// Copyright 2021 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.Extensions.Interfaces; + +namespace Keyfactor.Extensions.Orchestrator.RemoteFile.DER +{ + public class Reenrollment : ReenrollmentBase + { + internal override ICertificateStoreSerializer GetCertificateStoreSerializer(string storeProperties) + { + return new DERCertificateStoreSerializer(storeProperties); + } + + public Reenrollment(IPAMSecretResolver resolver) + { + _resolver = resolver; + } + } +} diff --git a/RemoteFile/ImplementedStoreTypes/JKS/Reenrollment.cs b/RemoteFile/ImplementedStoreTypes/JKS/Reenrollment.cs new file mode 100644 index 0000000..99c913f --- /dev/null +++ b/RemoteFile/ImplementedStoreTypes/JKS/Reenrollment.cs @@ -0,0 +1,24 @@ +// Copyright 2021 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.Extensions.Interfaces; + +namespace Keyfactor.Extensions.Orchestrator.RemoteFile.JKS +{ + public class Reenrollment : ReenrollmentBase + { + internal override ICertificateStoreSerializer GetCertificateStoreSerializer(string storeProperties) + { + return new JKSCertificateStoreSerializer(storeProperties); + } + + public Reenrollment(IPAMSecretResolver resolver) + { + _resolver = resolver; + } + } +} diff --git a/RemoteFile/ImplementedStoreTypes/KDB/Reenrollment.cs b/RemoteFile/ImplementedStoreTypes/KDB/Reenrollment.cs new file mode 100644 index 0000000..28a4360 --- /dev/null +++ b/RemoteFile/ImplementedStoreTypes/KDB/Reenrollment.cs @@ -0,0 +1,24 @@ +// Copyright 2021 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.Extensions.Interfaces; + +namespace Keyfactor.Extensions.Orchestrator.RemoteFile.KDB +{ + public class Reenrollment : ReenrollmentBase + { + internal override ICertificateStoreSerializer GetCertificateStoreSerializer(string storeProperties) + { + return new KDBCertificateStoreSerializer(storeProperties); + } + + public Reenrollment(IPAMSecretResolver resolver) + { + _resolver = resolver; + } + } +} diff --git a/RemoteFile/ImplementedStoreTypes/OraWlt/Reenrollment.cs b/RemoteFile/ImplementedStoreTypes/OraWlt/Reenrollment.cs new file mode 100644 index 0000000..230211a --- /dev/null +++ b/RemoteFile/ImplementedStoreTypes/OraWlt/Reenrollment.cs @@ -0,0 +1,25 @@ +// Copyright 2021 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.Extensions.Orchestrator.RemoteFile.KDB; +using Keyfactor.Orchestrators.Extensions.Interfaces; + +namespace Keyfactor.Extensions.Orchestrator.RemoteFile.OraWlt +{ + public class Reenrollment : ReenrollmentBase + { + internal override ICertificateStoreSerializer GetCertificateStoreSerializer(string storeProperties) + { + return new OraWltCertificateStoreSerializer(storeProperties); + } + + public Reenrollment(IPAMSecretResolver resolver) + { + _resolver = resolver; + } + } +} diff --git a/RemoteFile/ImplementedStoreTypes/PEM/Reenrollment.cs b/RemoteFile/ImplementedStoreTypes/PEM/Reenrollment.cs new file mode 100644 index 0000000..143913d --- /dev/null +++ b/RemoteFile/ImplementedStoreTypes/PEM/Reenrollment.cs @@ -0,0 +1,25 @@ +// Copyright 2021 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.Extensions.Orchestrator.RemoteFile.PEM; +using Keyfactor.Orchestrators.Extensions.Interfaces; + +namespace Keyfactor.Extensions.Orchestrator.RemoteFile.PEM +{ + public class Reenrollment : ReenrollmentBase + { + internal override ICertificateStoreSerializer GetCertificateStoreSerializer(string storeProperties) + { + return new PEMCertificateStoreSerializer(storeProperties); + } + + public Reenrollment(IPAMSecretResolver resolver) + { + _resolver = resolver; + } + } +} diff --git a/RemoteFile/ImplementedStoreTypes/PKCS12/Reenrollment.cs b/RemoteFile/ImplementedStoreTypes/PKCS12/Reenrollment.cs new file mode 100644 index 0000000..d9f4d40 --- /dev/null +++ b/RemoteFile/ImplementedStoreTypes/PKCS12/Reenrollment.cs @@ -0,0 +1,24 @@ +// Copyright 2021 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.Extensions.Interfaces; + +namespace Keyfactor.Extensions.Orchestrator.RemoteFile.PKCS12 +{ + public class Reenrollment : ReenrollmentBase + { + internal override ICertificateStoreSerializer GetCertificateStoreSerializer(string storeProperties) + { + return new PKCS12CertificateStoreSerializer(storeProperties); + } + + public Reenrollment(IPAMSecretResolver resolver) + { + _resolver = resolver; + } + } +} diff --git a/RemoteFile/InventoryBase.cs b/RemoteFile/InventoryBase.cs index 3931e77..7d6aa2f 100644 --- a/RemoteFile/InventoryBase.cs +++ b/RemoteFile/InventoryBase.cs @@ -21,7 +21,7 @@ namespace Keyfactor.Extensions.Orchestrator.RemoteFile { public abstract class InventoryBase : RemoteFileJobTypeBase, IInventoryJobExtension { - public string ExtensionName => string.Empty; + public string ExtensionName => "Keyfactor.Extensions.Orchestrator.RemoteFile.Inventory"; RemoteCertificateStore certificateStore = new RemoteCertificateStore(); diff --git a/RemoteFile/ManagementBase.cs b/RemoteFile/ManagementBase.cs index 524873f..175452b 100644 --- a/RemoteFile/ManagementBase.cs +++ b/RemoteFile/ManagementBase.cs @@ -8,7 +8,6 @@ using System; using System.Collections.Generic; using System.Security.Cryptography.X509Certificates; -using System.Threading; using Keyfactor.Logging; using Keyfactor.Orchestrators.Extensions; @@ -22,9 +21,7 @@ namespace Keyfactor.Extensions.Orchestrator.RemoteFile { public abstract class ManagementBase : RemoteFileJobTypeBase, IManagementJobExtension { - static Mutex mutex = new Mutex(false, "ModifyStore"); - - public string ExtensionName => ""; + public string ExtensionName => "Keyfactor.Extensions.Orchestrator.RemoteFile.Management"; internal RemoteCertificateStore certificateStore = new RemoteCertificateStore(); @@ -44,8 +41,6 @@ public JobResult ProcessJob(ManagementJobConfiguration config) try { - mutex.WaitOne(); - string userName = PAMUtilities.ResolvePAMField(_resolver, logger, "Server User Name", config.ServerUsername); string userPassword = PAMUtilities.ResolvePAMField(_resolver, logger, "Server Password", config.ServerPassword); string storePassword = PAMUtilities.ResolvePAMField(_resolver, logger, "Store Password", config.CertificateStoreDetails.StorePassword); @@ -118,8 +113,6 @@ public JobResult ProcessJob(ManagementJobConfiguration config) } finally { - mutex.ReleaseMutex(); - if (certificateStore.RemoteHandler != null) certificateStore.Terminate(); } diff --git a/RemoteFile/ReenrollmentBase.cs b/RemoteFile/ReenrollmentBase.cs new file mode 100644 index 0000000..915ae17 --- /dev/null +++ b/RemoteFile/ReenrollmentBase.cs @@ -0,0 +1,138 @@ +// Copyright 2021 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 System; +using System.Collections.Generic; +using System.Security.Cryptography.X509Certificates; + +using Keyfactor.Logging; +using Keyfactor.Orchestrators.Extensions; +using Keyfactor.Orchestrators.Common.Enums; + +using Microsoft.Extensions.Logging; + +using Newtonsoft.Json; + +namespace Keyfactor.Extensions.Orchestrator.RemoteFile +{ + public abstract class ReenrollmentBase : RemoteFileJobTypeBase, IReenrollmentJobExtension + { + public string ExtensionName => "Keyfactor.Extensions.Orchestrator.RemoteFile"; + + internal RemoteCertificateStore certificateStore = new RemoteCertificateStore(); + + 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($"Job Properties:"); + foreach (KeyValuePair keyValue in config.JobProperties == null ? new Dictionary() : config.JobProperties) + { + logger.LogDebug($" {keyValue.Key}: {keyValue.Value}"); + } + + ICertificateStoreSerializer certificateStoreSerializer = GetCertificateStoreSerializer(config.CertificateStoreDetails.Properties); + + try + { + string userName = PAMUtilities.ResolvePAMField(_resolver, logger, "Server User Name", config.ServerUsername); + string userPassword = PAMUtilities.ResolvePAMField(_resolver, logger, "Server Password", config.ServerPassword); + string storePassword = PAMUtilities.ResolvePAMField(_resolver, logger, "Store Password", config.CertificateStoreDetails.StorePassword); + + ApplicationSettings.Initialize(this.GetType().Assembly.Location); + dynamic properties = JsonConvert.DeserializeObject(config.CertificateStoreDetails.Properties.ToString()); + string sudoImpersonatedUser = properties.SudoImpersonatedUser == null || string.IsNullOrEmpty(properties.SudoImpersonatedUser.Value) ? + ApplicationSettings.DefaultSudoImpersonatedUser : + properties.SudoImpersonatedUser.Value; + + certificateStore = new RemoteCertificateStore(config.CertificateStoreDetails.ClientMachine, userName, userPassword, config.CertificateStoreDetails.StorePath, storePassword, config.JobProperties); + certificateStore.Initialize(sudoImpersonatedUser); + + PathFile storePathFile = RemoteCertificateStore.SplitStorePathFile(config.CertificateStoreDetails.StorePath); + + switch (config.OperationType) + { + case CertStoreOperationType.Add: + logger.LogDebug($"BEGIN add Operation for {config.CertificateStoreDetails.StorePath} on {config.CertificateStoreDetails.ClientMachine}."); + if (!certificateStore.DoesStoreExist()) + { + if (ApplicationSettings.CreateStoreIfMissing) + CreateStore(certificateStoreSerializer, config); + else + throw new RemoteFileException($"Certificate store {config.CertificateStoreDetails.StorePath} does not exist on server {config.CertificateStoreDetails.ClientMachine}."); + } + certificateStore.LoadCertificateStore(certificateStoreSerializer, config.CertificateStoreDetails.Properties, false); + certificateStore.AddCertificate((config.JobCertificate.Alias ?? new X509Certificate2(Convert.FromBase64String(config.JobCertificate.Contents), config.JobCertificate.PrivateKeyPassword, X509KeyStorageFlags.EphemeralKeySet).Thumbprint), config.JobCertificate.Contents, config.Overwrite, config.JobCertificate.PrivateKeyPassword); + certificateStore.SaveCertificateStore(certificateStoreSerializer.SerializeRemoteCertificateStore(certificateStore.GetCertificateStore(), storePathFile.Path, storePathFile.File, storePassword, certificateStore.RemoteHandler)); + + logger.LogDebug($"END add Operation for {config.CertificateStoreDetails.StorePath} on {config.CertificateStoreDetails.ClientMachine}."); + break; + + case CertStoreOperationType.Remove: + logger.LogDebug($"BEGIN Delete Operation for {config.CertificateStoreDetails.StorePath} on {config.CertificateStoreDetails.ClientMachine}."); + if (!certificateStore.DoesStoreExist()) + { + throw new RemoteFileException($"Certificate store {config.CertificateStoreDetails.StorePath} does not exist on server {config.CertificateStoreDetails.ClientMachine}."); + } + else + { + certificateStore.LoadCertificateStore(certificateStoreSerializer, config.CertificateStoreDetails.Properties, false); + certificateStore.DeleteCertificateByAlias(config.JobCertificate.Alias); + certificateStore.SaveCertificateStore(certificateStoreSerializer.SerializeRemoteCertificateStore(certificateStore.GetCertificateStore(), storePathFile.Path, storePathFile.File, storePassword, certificateStore.RemoteHandler)); + } + logger.LogDebug($"END Delete Operation for {config.CertificateStoreDetails.StorePath} on {config.CertificateStoreDetails.ClientMachine}."); + break; + + case CertStoreOperationType.Create: + logger.LogDebug($"BEGIN create Operation for {config.CertificateStoreDetails.StorePath} on {config.CertificateStoreDetails.ClientMachine}."); + if (certificateStore.DoesStoreExist()) + { + throw new RemoteFileException($"Certificate store {config.CertificateStoreDetails.StorePath} already exists."); + } + else + { + CreateStore(certificateStoreSerializer, config); + } + logger.LogDebug($"END create Operation for {config.CertificateStoreDetails.StorePath} on {config.CertificateStoreDetails.ClientMachine}."); + break; + + default: + return new JobResult() { Result = OrchestratorJobStatusJobResult.Failure, JobHistoryId = config.JobHistoryId, FailureMessage = $"Site {config.CertificateStoreDetails.StorePath} on server {config.CertificateStoreDetails.ClientMachine}: Unsupported operation: {config.OperationType.ToString()}" }; + } + } + catch (Exception ex) + { + logger.LogError($"Exception for {config.Capability}: {RemoteFileException.FlattenExceptionMessages(ex, string.Empty)} for job id {config.JobId}"); + return new JobResult() { Result = OrchestratorJobStatusJobResult.Failure, JobHistoryId = config.JobHistoryId, FailureMessage = RemoteFileException.FlattenExceptionMessages(ex, $"Site {config.CertificateStoreDetails.StorePath} on server {config.CertificateStoreDetails.ClientMachine}:") }; + } + finally + { + if (certificateStore.RemoteHandler != null) + certificateStore.Terminate(); + } + + logger.LogDebug($"...End {config.Capability} job for job id {config.JobId}"); + return new JobResult() { Result = OrchestratorJobStatusJobResult.Success, JobHistoryId = config.JobHistoryId }; + } + + private void CreateStore(ICertificateStoreSerializer certificateStoreSerializer, ManagementJobConfiguration config) + { + dynamic properties = JsonConvert.DeserializeObject(config.CertificateStoreDetails.Properties.ToString()); + string linuxFilePermissions = properties.LinuxFilePermissionsOnStoreCreation == null || string.IsNullOrEmpty(properties.LinuxFilePermissionsOnStoreCreation.Value) ? + ApplicationSettings.DefaultLinuxPermissionsOnStoreCreation : + properties.LinuxFilePermissionsOnStoreCreation.Value; + + string linuxFileOwner = properties.LinuxFileOwnerOnStoreCreation == null || string.IsNullOrEmpty(properties.LinuxFileOwnerOnStoreCreation.Value) ? + ApplicationSettings.DefaultOwnerOnStoreCreation : + properties.LinuxFileOwnerOnStoreCreation.Value; + + certificateStore.CreateCertificateStore(certificateStoreSerializer, config.CertificateStoreDetails.StorePath, linuxFilePermissions, string.IsNullOrEmpty(linuxFileOwner) ? config.ServerUsername : linuxFileOwner); + } + } +} diff --git a/RemoteFile/config.json b/RemoteFile/config.json index 3e638d7..e24b164 100644 --- a/RemoteFile/config.json +++ b/RemoteFile/config.json @@ -6,5 +6,6 @@ "SeparateUploadFilePath": "", "FileTransferProtocol": "SCP", "DefaultLinuxPermissionsOnStoreCreation": "600", - "DefaultOwnerOnStoreCreation": "" + "DefaultOwnerOnStoreCreation": "", + "CreateCSROnDevice": "N" } \ No newline at end of file From a14c19169bce7e8250ad472d09f0768c774160c0 Mon Sep 17 00:00:00 2001 From: leefine02 Date: Mon, 24 Jun 2024 20:26:24 +0000 Subject: [PATCH 08/20] ab#59979 --- RemoteFile/ReenrollmentBase.cs | 140 +++++++++++++++++++-------------- 1 file changed, 79 insertions(+), 61 deletions(-) diff --git a/RemoteFile/ReenrollmentBase.cs b/RemoteFile/ReenrollmentBase.cs index 915ae17..f3b0354 100644 --- a/RemoteFile/ReenrollmentBase.cs +++ b/RemoteFile/ReenrollmentBase.cs @@ -16,6 +16,14 @@ using Microsoft.Extensions.Logging; using Newtonsoft.Json; +using static Org.BouncyCastle.Math.EC.ECCurve; +using Org.BouncyCastle.Asn1.Pkcs; +using Org.BouncyCastle.Asn1.X509; +using Org.BouncyCastle.Asn1; +using Org.BouncyCastle.Crypto.Generators; +using Org.BouncyCastle.Crypto.Prng; +using Org.BouncyCastle.Crypto; +using Org.BouncyCastle.Pkcs; namespace Keyfactor.Extensions.Orchestrator.RemoteFile { @@ -25,12 +33,13 @@ public abstract class ReenrollmentBase : RemoteFileJobTypeBase, IReenrollmentJob internal RemoteCertificateStore certificateStore = new RemoteCertificateStore(); - public JobResult ProcessJob(ManagementJobConfiguration config) + public JobResult ProcessJob(ReenrollmentJobConfiguration config, SubmitReenrollmentCSR submitReenrollmentUpdate) { 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($"Server: {config.CertificateStoreDetails.ClientMachine}"); + logger.LogDebug($"Store Path: {config.CertificateStoreDetails.StorePath}"); + logger.LogDebug($"Job Properties:"); foreach (KeyValuePair keyValue in config.JobProperties == null ? new Dictionary() : config.JobProperties) { @@ -50,62 +59,35 @@ public JobResult ProcessJob(ManagementJobConfiguration config) string sudoImpersonatedUser = properties.SudoImpersonatedUser == null || string.IsNullOrEmpty(properties.SudoImpersonatedUser.Value) ? ApplicationSettings.DefaultSudoImpersonatedUser : properties.SudoImpersonatedUser.Value; + bool createCSROnDevice = properties.CreateCSROnDevice == null || string.IsNullOrEmpty(properties.CreateCSROnDevice.Value) ? + ApplicationSettings.CreateCSROnDevice : + properties.CreateCSROnDevice.Value; certificateStore = new RemoteCertificateStore(config.CertificateStoreDetails.ClientMachine, userName, userPassword, config.CertificateStoreDetails.StorePath, storePassword, config.JobProperties); certificateStore.Initialize(sudoImpersonatedUser); PathFile storePathFile = RemoteCertificateStore.SplitStorePathFile(config.CertificateStoreDetails.StorePath); - switch (config.OperationType) + if (!certificateStore.DoesStoreExist()) + { + throw new RemoteFileException($"Certificate store {config.CertificateStoreDetails.StorePath} does not exist on server {config.CertificateStoreDetails.ClientMachine}."); + } + + certificateStore.LoadCertificateStore(certificateStoreSerializer, config.CertificateStoreDetails.Properties, false); + if (createCSROnDevice) { - case CertStoreOperationType.Add: - logger.LogDebug($"BEGIN add Operation for {config.CertificateStoreDetails.StorePath} on {config.CertificateStoreDetails.ClientMachine}."); - if (!certificateStore.DoesStoreExist()) - { - if (ApplicationSettings.CreateStoreIfMissing) - CreateStore(certificateStoreSerializer, config); - else - throw new RemoteFileException($"Certificate store {config.CertificateStoreDetails.StorePath} does not exist on server {config.CertificateStoreDetails.ClientMachine}."); - } - certificateStore.LoadCertificateStore(certificateStoreSerializer, config.CertificateStoreDetails.Properties, false); - certificateStore.AddCertificate((config.JobCertificate.Alias ?? new X509Certificate2(Convert.FromBase64String(config.JobCertificate.Contents), config.JobCertificate.PrivateKeyPassword, X509KeyStorageFlags.EphemeralKeySet).Thumbprint), config.JobCertificate.Contents, config.Overwrite, config.JobCertificate.PrivateKeyPassword); - certificateStore.SaveCertificateStore(certificateStoreSerializer.SerializeRemoteCertificateStore(certificateStore.GetCertificateStore(), storePathFile.Path, storePathFile.File, storePassword, certificateStore.RemoteHandler)); - - logger.LogDebug($"END add Operation for {config.CertificateStoreDetails.StorePath} on {config.CertificateStoreDetails.ClientMachine}."); - break; - - case CertStoreOperationType.Remove: - logger.LogDebug($"BEGIN Delete Operation for {config.CertificateStoreDetails.StorePath} on {config.CertificateStoreDetails.ClientMachine}."); - if (!certificateStore.DoesStoreExist()) - { - throw new RemoteFileException($"Certificate store {config.CertificateStoreDetails.StorePath} does not exist on server {config.CertificateStoreDetails.ClientMachine}."); - } - else - { - certificateStore.LoadCertificateStore(certificateStoreSerializer, config.CertificateStoreDetails.Properties, false); - certificateStore.DeleteCertificateByAlias(config.JobCertificate.Alias); - certificateStore.SaveCertificateStore(certificateStoreSerializer.SerializeRemoteCertificateStore(certificateStore.GetCertificateStore(), storePathFile.Path, storePathFile.File, storePassword, certificateStore.RemoteHandler)); - } - logger.LogDebug($"END Delete Operation for {config.CertificateStoreDetails.StorePath} on {config.CertificateStoreDetails.ClientMachine}."); - break; - - case CertStoreOperationType.Create: - logger.LogDebug($"BEGIN create Operation for {config.CertificateStoreDetails.StorePath} on {config.CertificateStoreDetails.ClientMachine}."); - if (certificateStore.DoesStoreExist()) - { - throw new RemoteFileException($"Certificate store {config.CertificateStoreDetails.StorePath} already exists."); - } - else - { - CreateStore(certificateStoreSerializer, config); - } - logger.LogDebug($"END create Operation for {config.CertificateStoreDetails.StorePath} on {config.CertificateStoreDetails.ClientMachine}."); - break; - - default: - return new JobResult() { Result = OrchestratorJobStatusJobResult.Failure, JobHistoryId = config.JobHistoryId, FailureMessage = $"Site {config.CertificateStoreDetails.StorePath} on server {config.CertificateStoreDetails.ClientMachine}: Unsupported operation: {config.OperationType.ToString()}" }; + config. } + else + { + + } + certificateStore.AddCertificate((config.JobCertificate.Alias ?? new X509Certificate2(Convert.FromBase64String(config.JobCertificate.Contents), config.JobCertificate.PrivateKeyPassword, X509KeyStorageFlags.EphemeralKeySet).Thumbprint), config.JobCertificate.Contents, config.Overwrite, config.JobCertificate.PrivateKeyPassword); + certificateStore.SaveCertificateStore(certificateStoreSerializer.SerializeRemoteCertificateStore(certificateStore.GetCertificateStore(), storePathFile.Path, storePathFile.File, storePassword, certificateStore.RemoteHandler)); + + logger.LogDebug($"END add Operation for {config.CertificateStoreDetails.StorePath} on {config.CertificateStoreDetails.ClientMachine}."); } + catch (Exception ex) { logger.LogError($"Exception for {config.Capability}: {RemoteFileException.FlattenExceptionMessages(ex, string.Empty)} for job id {config.JobId}"); @@ -121,18 +103,54 @@ public JobResult ProcessJob(ManagementJobConfiguration config) return new JobResult() { Result = OrchestratorJobStatusJobResult.Success, JobHistoryId = config.JobHistoryId }; } - private void CreateStore(ICertificateStoreSerializer certificateStoreSerializer, ManagementJobConfiguration config) + private string GenerateCSR(string subjectText, List sans) { - dynamic properties = JsonConvert.DeserializeObject(config.CertificateStoreDetails.Properties.ToString()); - string linuxFilePermissions = properties.LinuxFilePermissionsOnStoreCreation == null || string.IsNullOrEmpty(properties.LinuxFilePermissionsOnStoreCreation.Value) ? - ApplicationSettings.DefaultLinuxPermissionsOnStoreCreation : - properties.LinuxFilePermissionsOnStoreCreation.Value; - - string linuxFileOwner = properties.LinuxFileOwnerOnStoreCreation == null || string.IsNullOrEmpty(properties.LinuxFileOwnerOnStoreCreation.Value) ? - ApplicationSettings.DefaultOwnerOnStoreCreation : - properties.LinuxFileOwnerOnStoreCreation.Value; - - certificateStore.CreateCertificateStore(certificateStoreSerializer, config.CertificateStoreDetails.StorePath, linuxFilePermissions, string.IsNullOrEmpty(linuxFileOwner) ? config.ServerUsername : linuxFileOwner); + //Code logic to: + // 1) Generate a new CSR + // 2) Include the provided subject text + // 3) Include the list of SANs + // 3) Include the OID corresponding to a Time Stamping request, so Command recognizes this as a request for re-enrollment + // 4) Return the base64 encoded CSR. + + // this approach relies on the Bouncy Castle Crypto package, and not the Microsoft x509 certificate libraries. + + var keyGenParams = new KeyGenerationParameters(new Org.BouncyCastle.Security.SecureRandom(new CryptoApiRandomGenerator()), 4096); + var keyPairGenerator = new RsaKeyPairGenerator(); + Org.BouncyCastle.Crypto.Generators. + keyPairGenerator.Init(keyGenParams); + + var keyPair = keyPairGenerator.GenerateKeyPair(); + var subject = new X509Name(subjectText); + + // Add SAN entries + var subAltNameList = new List(); + sans.ForEach(san => subAltNameList.Add(new GeneralName(GeneralName.DnsName, san.Trim()))); + var generalSubAltNames = new GeneralNames(subAltNameList.ToArray()); + + // Create Key Usage attribute + int keyUsage = KeyUsage.DigitalSignature | KeyUsage.NonRepudiation; + var keyUsageExtension = new KeyUsage(keyUsage); + + // Add Extended Key Usage extension for re-enrollment (1.3.6.1.5.5.7.3.8 is the OID for time stamping, the Command CA should be configured to recognize a CSR with this OID as a request for re-enrollment) + //var timestampOid = new DerObjectIdentifier("1.3.6.1.5.5.7.3.8"); // https://oidref.com/1.3.6.1.5.5.7.3.8 + //var extendedKeyUsage = new ExtendedKeyUsage(new DerObjectIdentifier[] { timestampOid }); + + // Create extensions + var extensionsGenerator = new X509ExtensionsGenerator(); + extensionsGenerator.AddExtension(X509Extensions.SubjectAlternativeName, false, generalSubAltNames); + extensionsGenerator.AddExtension(X509Extensions.KeyUsage, true, keyUsageExtension); + extensionsGenerator.AddExtension(X509Extensions.ExtendedKeyUsage, false, extendedKeyUsage); + X509Extensions extensions = extensionsGenerator.Generate(); + + // Create attribute set with extensions + var attributeSet = new AttributePkcs(PkcsObjectIdentifiers.Pkcs9AtExtensionRequest, new DerSet(extensions)); + + // Include the attributes in the request + var csr = new Pkcs10CertificationRequest(PkcsObjectIdentifiers.Pkcs9AtExtensionRequest.Id, subject, keyPair.Public, new DerSet(attributeSet), keyPair.Private); + + // encode the CSR as base64 + var encodedCsr = Convert.ToBase64String(csr.GetEncoded()); + return encodedCsr; } } } From a5c1554701b932f2cbd8b0d16f53214affd5b40f Mon Sep 17 00:00:00 2001 From: leefine02 Date: Tue, 25 Jun 2024 18:56:45 +0000 Subject: [PATCH 09/20] ab#59979 --- RemoteFile/ReenrollmentBase.cs | 44 +++++++++++++++++++++++++++------- 1 file changed, 35 insertions(+), 9 deletions(-) diff --git a/RemoteFile/ReenrollmentBase.cs b/RemoteFile/ReenrollmentBase.cs index f3b0354..209c436 100644 --- a/RemoteFile/ReenrollmentBase.cs +++ b/RemoteFile/ReenrollmentBase.cs @@ -24,6 +24,7 @@ using Org.BouncyCastle.Crypto.Prng; using Org.BouncyCastle.Crypto; using Org.BouncyCastle.Pkcs; +using System.Linq; namespace Keyfactor.Extensions.Orchestrator.RemoteFile { @@ -33,6 +34,12 @@ public abstract class ReenrollmentBase : RemoteFileJobTypeBase, IReenrollmentJob internal RemoteCertificateStore certificateStore = new RemoteCertificateStore(); + internal enum SupportedKeyTypeEnum + { + RSA, + ECC + } + public JobResult ProcessJob(ReenrollmentJobConfiguration config, SubmitReenrollmentCSR submitReenrollmentUpdate) { ILogger logger = LogHandler.GetClassLogger(this.GetType()); @@ -59,10 +66,20 @@ public JobResult ProcessJob(ReenrollmentJobConfiguration config, SubmitReenrollm string sudoImpersonatedUser = properties.SudoImpersonatedUser == null || string.IsNullOrEmpty(properties.SudoImpersonatedUser.Value) ? ApplicationSettings.DefaultSudoImpersonatedUser : properties.SudoImpersonatedUser.Value; - bool createCSROnDevice = properties.CreateCSROnDevice == null || string.IsNullOrEmpty(properties.CreateCSROnDevice.Value) ? - ApplicationSettings.CreateCSROnDevice : + bool createCSROnDevice = properties.CreateCSROnDevice == null || string.IsNullOrEmpty(properties.CreateCSROnDevice.Value) ? + ApplicationSettings.CreateCSROnDevice : properties.CreateCSROnDevice.Value; + string keyType = !config.JobProperties.ContainsKey("keyType") || config.JobProperties["keyType"] == null || string.IsNullOrEmpty(config.JobProperties["keyType"].ToString()) ? string.Empty : config.JobProperties["keyType"].ToString(); + int? keySize = !config.JobProperties.ContainsKey("keySize") || config.JobProperties["keySize"] == null || string.IsNullOrEmpty(config.JobProperties["keySize"].ToString()) ? null : Convert.ToInt32(config.JobProperties["keySize"]); + string subjectText = !config.JobProperties.ContainsKey("subjectText") || config.JobProperties["subjectText"] == null || config.JobProperties["subjectText"] == null || string.IsNullOrEmpty(config.JobProperties["subjectText"].ToString()) ? string.Empty : config.JobProperties["subjectText"].ToString(); + string sans = !config.JobProperties.ContainsKey("SANs") || config.JobProperties["SANs"] == null || string.IsNullOrEmpty(config.JobProperties["SANs"].ToString()) ? string.Empty : config.JobProperties["SANs"].ToString(); + + string keyTypes = string.Join(",", Enum.GetNames(typeof(SupportedKeyTypeEnum))); + if (!Enum.TryParse(keyType.ToUpper(), out SupportedKeyTypeEnum keyTypeEnum)) + { + throw new RemoteFileException($"Unsupported KeyType value {keyType}. Supported types are {keyTypes}."); + } certificateStore = new RemoteCertificateStore(config.CertificateStoreDetails.ClientMachine, userName, userPassword, config.CertificateStoreDetails.StorePath, storePassword, config.JobProperties); certificateStore.Initialize(sudoImpersonatedUser); @@ -76,11 +93,11 @@ public JobResult ProcessJob(ReenrollmentJobConfiguration config, SubmitReenrollm certificateStore.LoadCertificateStore(certificateStoreSerializer, config.CertificateStoreDetails.Properties, false); if (createCSROnDevice) { - config. + throw new Exception("Not implemented"); } else { - + string csr = GenerateCSR } certificateStore.AddCertificate((config.JobCertificate.Alias ?? new X509Certificate2(Convert.FromBase64String(config.JobCertificate.Contents), config.JobCertificate.PrivateKeyPassword, X509KeyStorageFlags.EphemeralKeySet).Thumbprint), config.JobCertificate.Contents, config.Overwrite, config.JobCertificate.PrivateKeyPassword); certificateStore.SaveCertificateStore(certificateStoreSerializer.SerializeRemoteCertificateStore(certificateStore.GetCertificateStore(), storePathFile.Path, storePathFile.File, storePassword, certificateStore.RemoteHandler)); @@ -103,7 +120,7 @@ public JobResult ProcessJob(ReenrollmentJobConfiguration config, SubmitReenrollm return new JobResult() { Result = OrchestratorJobStatusJobResult.Success, JobHistoryId = config.JobHistoryId }; } - private string GenerateCSR(string subjectText, List sans) + private string GenerateCSR(string subjectText, SupportedKeyTypeEnum keyType, int keySize, List sans) { //Code logic to: // 1) Generate a new CSR @@ -114,9 +131,18 @@ private string GenerateCSR(string subjectText, List sans) // this approach relies on the Bouncy Castle Crypto package, and not the Microsoft x509 certificate libraries. - var keyGenParams = new KeyGenerationParameters(new Org.BouncyCastle.Security.SecureRandom(new CryptoApiRandomGenerator()), 4096); - var keyPairGenerator = new RsaKeyPairGenerator(); - Org.BouncyCastle.Crypto.Generators. + IAsymmetricCipherKeyPairGenerator keyPairGenerator = null; + switch (keyType) + { + case SupportedKeyTypeEnum.RSA: + keyPairGenerator = new RsaKeyPairGenerator(); + break; + case SupportedKeyTypeEnum.ECC: + keyPairGenerator = new ECKeyPairGenerator(); + break; + } + + var keyGenParams = new KeyGenerationParameters(new Org.BouncyCastle.Security.SecureRandom(new CryptoApiRandomGenerator()), keySize); keyPairGenerator.Init(keyGenParams); var keyPair = keyPairGenerator.GenerateKeyPair(); @@ -139,7 +165,7 @@ private string GenerateCSR(string subjectText, List sans) var extensionsGenerator = new X509ExtensionsGenerator(); extensionsGenerator.AddExtension(X509Extensions.SubjectAlternativeName, false, generalSubAltNames); extensionsGenerator.AddExtension(X509Extensions.KeyUsage, true, keyUsageExtension); - extensionsGenerator.AddExtension(X509Extensions.ExtendedKeyUsage, false, extendedKeyUsage); + //extensionsGenerator.AddExtension(X509Extensions.ExtendedKeyUsage, false, extendedKeyUsage); X509Extensions extensions = extensionsGenerator.Generate(); // Create attribute set with extensions From e1b4b91d7b1cf10a81bb7c93bd4ce4ebacfc39fc Mon Sep 17 00:00:00 2001 From: leefine02 Date: Wed, 26 Jun 2024 20:35:22 +0000 Subject: [PATCH 10/20] ab#59979 --- RemoteFile/ReenrollmentBase.cs | 35 +++++++++++++++++++++++----------- 1 file changed, 24 insertions(+), 11 deletions(-) diff --git a/RemoteFile/ReenrollmentBase.cs b/RemoteFile/ReenrollmentBase.cs index 209c436..5b486ce 100644 --- a/RemoteFile/ReenrollmentBase.cs +++ b/RemoteFile/ReenrollmentBase.cs @@ -40,7 +40,7 @@ internal enum SupportedKeyTypeEnum ECC } - public JobResult ProcessJob(ReenrollmentJobConfiguration config, SubmitReenrollmentCSR submitReenrollmentUpdate) + public JobResult ProcessJob(ReenrollmentJobConfiguration config, SubmitReenrollmentCSR submitReenrollment) { ILogger logger = LogHandler.GetClassLogger(this.GetType()); logger.LogDebug($"Begin {config.Capability} for job id {config.JobId}..."); @@ -71,15 +71,36 @@ public JobResult ProcessJob(ReenrollmentJobConfiguration config, SubmitReenrollm properties.CreateCSROnDevice.Value; string keyType = !config.JobProperties.ContainsKey("keyType") || config.JobProperties["keyType"] == null || string.IsNullOrEmpty(config.JobProperties["keyType"].ToString()) ? string.Empty : config.JobProperties["keyType"].ToString(); - int? keySize = !config.JobProperties.ContainsKey("keySize") || config.JobProperties["keySize"] == null || string.IsNullOrEmpty(config.JobProperties["keySize"].ToString()) ? null : Convert.ToInt32(config.JobProperties["keySize"]); + int keySize = !config.JobProperties.ContainsKey("keySize") || config.JobProperties["keySize"] == null || string.IsNullOrEmpty(config.JobProperties["keySize"].ToString()) ? 2048 : Convert.ToInt32(config.JobProperties["keySize"]); string subjectText = !config.JobProperties.ContainsKey("subjectText") || config.JobProperties["subjectText"] == null || config.JobProperties["subjectText"] == null || string.IsNullOrEmpty(config.JobProperties["subjectText"].ToString()) ? string.Empty : config.JobProperties["subjectText"].ToString(); string sans = !config.JobProperties.ContainsKey("SANs") || config.JobProperties["SANs"] == null || string.IsNullOrEmpty(config.JobProperties["SANs"].ToString()) ? string.Empty : config.JobProperties["SANs"].ToString(); + //TODO - Set Alias and Overwrite "for real" once product figures out how to pass that + string alias = "abcd"; + bool overwrite = true; + + // validate parameters string keyTypes = string.Join(",", Enum.GetNames(typeof(SupportedKeyTypeEnum))); if (!Enum.TryParse(keyType.ToUpper(), out SupportedKeyTypeEnum keyTypeEnum)) { throw new RemoteFileException($"Unsupported KeyType value {keyType}. Supported types are {keyTypes}."); } + + // generate CSR and call back to enroll certificate + string csr = string.Empty; + if (createCSROnDevice) + { + throw new Exception("Not implemented"); + } + else + { + csr = GenerateCSR(subjectText, keyTypeEnum, keySize, new List(sans.Split('&', StringSplitOptions.RemoveEmptyEntries))); + } + + X509Certificate2 cert = submitReenrollment.Invoke(csr); + + + // save certificate certificateStore = new RemoteCertificateStore(config.CertificateStoreDetails.ClientMachine, userName, userPassword, config.CertificateStoreDetails.StorePath, storePassword, config.JobProperties); certificateStore.Initialize(sudoImpersonatedUser); @@ -91,15 +112,7 @@ public JobResult ProcessJob(ReenrollmentJobConfiguration config, SubmitReenrollm } certificateStore.LoadCertificateStore(certificateStoreSerializer, config.CertificateStoreDetails.Properties, false); - if (createCSROnDevice) - { - throw new Exception("Not implemented"); - } - else - { - string csr = GenerateCSR - } - certificateStore.AddCertificate((config.JobCertificate.Alias ?? new X509Certificate2(Convert.FromBase64String(config.JobCertificate.Contents), config.JobCertificate.PrivateKeyPassword, X509KeyStorageFlags.EphemeralKeySet).Thumbprint), config.JobCertificate.Contents, config.Overwrite, config.JobCertificate.PrivateKeyPassword); + certificateStore.AddCertificate((alias ?? cert.Thumbprint), config.JobCertificate.Contents, overwrite, null); certificateStore.SaveCertificateStore(certificateStoreSerializer.SerializeRemoteCertificateStore(certificateStore.GetCertificateStore(), storePathFile.Path, storePathFile.File, storePassword, certificateStore.RemoteHandler)); logger.LogDebug($"END add Operation for {config.CertificateStoreDetails.StorePath} on {config.CertificateStoreDetails.ClientMachine}."); From 271d9214d6878c06c1f4525c77d78d3bc41f1a73 Mon Sep 17 00:00:00 2001 From: leefine02 Date: Mon, 1 Jul 2024 19:45:45 +0000 Subject: [PATCH 11/20] ab#59979 --- RemoteFile/ReenrollmentBase.cs | 109 +++++++++++++++++++++++---------- RemoteFile/manifest.json | 24 ++++++++ 2 files changed, 101 insertions(+), 32 deletions(-) diff --git a/RemoteFile/ReenrollmentBase.cs b/RemoteFile/ReenrollmentBase.cs index 5b486ce..4de5e67 100644 --- a/RemoteFile/ReenrollmentBase.cs +++ b/RemoteFile/ReenrollmentBase.cs @@ -25,6 +25,7 @@ using Org.BouncyCastle.Crypto; using Org.BouncyCastle.Pkcs; using System.Linq; +using System.Security.Cryptography.Pkcs; namespace Keyfactor.Extensions.Orchestrator.RemoteFile { @@ -68,15 +69,15 @@ public JobResult ProcessJob(ReenrollmentJobConfiguration config, SubmitReenrollm properties.SudoImpersonatedUser.Value; bool createCSROnDevice = properties.CreateCSROnDevice == null || string.IsNullOrEmpty(properties.CreateCSROnDevice.Value) ? ApplicationSettings.CreateCSROnDevice : - properties.CreateCSROnDevice.Value; + Convert.ToBoolean(properties.CreateCSROnDevice.Value); string keyType = !config.JobProperties.ContainsKey("keyType") || config.JobProperties["keyType"] == null || string.IsNullOrEmpty(config.JobProperties["keyType"].ToString()) ? string.Empty : config.JobProperties["keyType"].ToString(); int keySize = !config.JobProperties.ContainsKey("keySize") || config.JobProperties["keySize"] == null || string.IsNullOrEmpty(config.JobProperties["keySize"].ToString()) ? 2048 : Convert.ToInt32(config.JobProperties["keySize"]); string subjectText = !config.JobProperties.ContainsKey("subjectText") || config.JobProperties["subjectText"] == null || config.JobProperties["subjectText"] == null || string.IsNullOrEmpty(config.JobProperties["subjectText"].ToString()) ? string.Empty : config.JobProperties["subjectText"].ToString(); - string sans = !config.JobProperties.ContainsKey("SANs") || config.JobProperties["SANs"] == null || string.IsNullOrEmpty(config.JobProperties["SANs"].ToString()) ? string.Empty : config.JobProperties["SANs"].ToString(); - //TODO - Set Alias and Overwrite "for real" once product figures out how to pass that + //TODO - Set SANs, Alias and Overwrite "for real" once product figures out how to pass that string alias = "abcd"; + string sans = "reenroll2.Keyfactor.com&reenroll1.keyfactor.com&reenroll3.Keyfactor.com"; bool overwrite = true; // validate parameters @@ -112,7 +113,7 @@ public JobResult ProcessJob(ReenrollmentJobConfiguration config, SubmitReenrollm } certificateStore.LoadCertificateStore(certificateStoreSerializer, config.CertificateStoreDetails.Properties, false); - certificateStore.AddCertificate((alias ?? cert.Thumbprint), config.JobCertificate.Contents, overwrite, null); + certificateStore.AddCertificate((alias ?? cert.Thumbprint), Convert.ToBase64String(cert.Export(X509ContentType.Cert)), overwrite, null); certificateStore.SaveCertificateStore(certificateStoreSerializer.SerializeRemoteCertificateStore(certificateStore.GetCertificateStore(), storePathFile.Path, storePathFile.File, storePassword, certificateStore.RemoteHandler)); logger.LogDebug($"END add Operation for {config.CertificateStoreDetails.StorePath} on {config.CertificateStoreDetails.ClientMachine}."); @@ -133,32 +134,28 @@ public JobResult ProcessJob(ReenrollmentJobConfiguration config, SubmitReenrollm return new JobResult() { Result = OrchestratorJobStatusJobResult.Success, JobHistoryId = config.JobHistoryId }; } - private string GenerateCSR(string subjectText, SupportedKeyTypeEnum keyType, int keySize, List sans) + internal string GenerateCSR(string subjectText, SupportedKeyTypeEnum keyType, int keySize, List sans) { - //Code logic to: - // 1) Generate a new CSR - // 2) Include the provided subject text - // 3) Include the list of SANs - // 3) Include the OID corresponding to a Time Stamping request, so Command recognizes this as a request for re-enrollment - // 4) Return the base64 encoded CSR. - - // this approach relies on the Bouncy Castle Crypto package, and not the Microsoft x509 certificate libraries. - IAsymmetricCipherKeyPairGenerator keyPairGenerator = null; + string algorithm = string.Empty; switch (keyType) { case SupportedKeyTypeEnum.RSA: keyPairGenerator = new RsaKeyPairGenerator(); + algorithm = "SHA256withRSA"; break; case SupportedKeyTypeEnum.ECC: keyPairGenerator = new ECKeyPairGenerator(); + algorithm = "SHA256withECDSA"; + if (keySize == 384) algorithm = "SHA384withECDSA"; + if (keySize == 521) algorithm = "SHA512withECDSA"; break; } var keyGenParams = new KeyGenerationParameters(new Org.BouncyCastle.Security.SecureRandom(new CryptoApiRandomGenerator()), keySize); keyPairGenerator.Init(keyGenParams); - var keyPair = keyPairGenerator.GenerateKeyPair(); + var subject = new X509Name(subjectText); // Add SAN entries @@ -166,30 +163,78 @@ private string GenerateCSR(string subjectText, SupportedKeyTypeEnum keyType, int sans.ForEach(san => subAltNameList.Add(new GeneralName(GeneralName.DnsName, san.Trim()))); var generalSubAltNames = new GeneralNames(subAltNameList.ToArray()); - // Create Key Usage attribute - int keyUsage = KeyUsage.DigitalSignature | KeyUsage.NonRepudiation; - var keyUsageExtension = new KeyUsage(keyUsage); - - // Add Extended Key Usage extension for re-enrollment (1.3.6.1.5.5.7.3.8 is the OID for time stamping, the Command CA should be configured to recognize a CSR with this OID as a request for re-enrollment) - //var timestampOid = new DerObjectIdentifier("1.3.6.1.5.5.7.3.8"); // https://oidref.com/1.3.6.1.5.5.7.3.8 - //var extendedKeyUsage = new ExtendedKeyUsage(new DerObjectIdentifier[] { timestampOid }); - - // Create extensions var extensionsGenerator = new X509ExtensionsGenerator(); extensionsGenerator.AddExtension(X509Extensions.SubjectAlternativeName, false, generalSubAltNames); - extensionsGenerator.AddExtension(X509Extensions.KeyUsage, true, keyUsageExtension); - //extensionsGenerator.AddExtension(X509Extensions.ExtendedKeyUsage, false, extendedKeyUsage); - X509Extensions extensions = extensionsGenerator.Generate(); - - // Create attribute set with extensions - var attributeSet = new AttributePkcs(PkcsObjectIdentifiers.Pkcs9AtExtensionRequest, new DerSet(extensions)); + var attributeSet = new DerSet(new AttributePkcs(PkcsObjectIdentifiers.Pkcs9AtExtensionRequest, new DerSet(extensionsGenerator.Generate()))); - // Include the attributes in the request - var csr = new Pkcs10CertificationRequest(PkcsObjectIdentifiers.Pkcs9AtExtensionRequest.Id, subject, keyPair.Public, new DerSet(attributeSet), keyPair.Private); + Pkcs10CertificationRequest csr = new Pkcs10CertificationRequest(algorithm, subject, keyPair.Public, (DerSet)attributeSet, keyPair.Private); // encode the CSR as base64 var encodedCsr = Convert.ToBase64String(csr.GetEncoded()); return encodedCsr; } + + + //private string GenerateCSR(string subjectText, SupportedKeyTypeEnum keyType, int keySize, List sans) + //{ + // //Code logic to: + // // 1) Generate a new CSR + // // 2) Include the provided subject text + // // 3) Include the list of SANs + // // 3) Include the OID corresponding to a Time Stamping request, so Command recognizes this as a request for re-enrollment + // // 4) Return the base64 encoded CSR. + + // // this approach relies on the Bouncy Castle Crypto package, and not the Microsoft x509 certificate libraries. + + // IAsymmetricCipherKeyPairGenerator keyPairGenerator = null; + // switch (keyType) + // { + // case SupportedKeyTypeEnum.RSA: + // keyPairGenerator = new RsaKeyPairGenerator(); + // break; + // case SupportedKeyTypeEnum.ECC: + // keyPairGenerator = new ECKeyPairGenerator(); + // break; + // } + + // var keyGenParams = new KeyGenerationParameters(new Org.BouncyCastle.Security.SecureRandom(new CryptoApiRandomGenerator()), keySize); + // keyPairGenerator.Init(keyGenParams); + + // var keyPair = keyPairGenerator.GenerateKeyPair(); + // var subject = new X509Name(subjectText); + + // // Add SAN entries + // var subAltNameList = new List(); + // sans.ForEach(san => subAltNameList.Add(new GeneralName(GeneralName.DnsName, san.Trim()))); + // var generalSubAltNames = new GeneralNames(subAltNameList.ToArray()); + + // // Create Key Usage attribute + // //int keyUsage = KeyUsage.DigitalSignature | KeyUsage.NonRepudiation; + // //var keyUsageExtension = new KeyUsage(keyUsage); + + // // Add Extended Key Usage extension for re-enrollment (1.3.6.1.5.5.7.3.8 is the OID for time stamping, the Command CA should be configured to recognize a CSR with this OID as a request for re-enrollment) + // //var timestampOid = new DerObjectIdentifier("1.3.6.1.5.5.7.3.8"); // https://oidref.com/1.3.6.1.5.5.7.3.8 + // //var extendedKeyUsage = new ExtendedKeyUsage(new DerObjectIdentifier[] { timestampOid }); + + // // Create extensions + // var extensionsGenerator = new X509ExtensionsGenerator(); + // extensionsGenerator.AddExtension(X509Extensions.SubjectAlternativeName, false, generalSubAltNames); + // //extensionsGenerator.AddExtension(X509Extensions.KeyUsage, true, keyUsageExtension); + // //extensionsGenerator.AddExtension(X509Extensions.ExtendedKeyUsage, false, extendedKeyUsage); + // X509Extensions extensions = extensionsGenerator.Generate(); + + // // Create attribute set with extensions + // //var attributeSet = new AttributePkcs(PkcsObjectIdentifiers.Pkcs9AtExtensionRequest, new DerSet(extensions)); + // var attribute = new DerSet(new AttributePkcs(PkcsObjectIdentifiers.Pkcs9AtExtensionRequest, new DerSet(extensions))); + // var attributeSet = new DerSet(new AttributePkcs(PkcsObjectIdentifiers.Pkcs9AtExtensionRequest, new DerSet(extensionsGenerator.Generate()))); + + // // Include the attributes in the request + // var csr = new Pkcs10CertificationRequest(PkcsObjectIdentifiers.Pkcs9AtExtensionRequest.Id, subject, keyPair.Public, attributeSet, keyPair.Private); + // //var csr = new Pkcs10CertificationRequest(PkcsObjectIdentifiers.Pkcs9AtExtensionRequest.Id, subject, keyPair.Public, new DerSet(attributeSet), keyPair.Private); + + // // encode the CSR as base64 + // var encodedCsr = Convert.ToBase64String(csr.GetEncoded()); + // return encodedCsr; + //} } } diff --git a/RemoteFile/manifest.json b/RemoteFile/manifest.json index c4e40bd..2bf4f13 100644 --- a/RemoteFile/manifest.json +++ b/RemoteFile/manifest.json @@ -9,6 +9,10 @@ "assemblypath": "RemoteFile.dll", "TypeFullName": "Keyfactor.Extensions.Orchestrator.RemoteFile.PKCS12.Management" }, + "CertStores.RFPkcs12.Reenrollment": { + "assemblypath": "RemoteFile.dll", + "TypeFullName": "Keyfactor.Extensions.Orchestrator.RemoteFile.PKCS12Reenrollment" + }, "CertStores.RFPkcs12.Discovery": { "assemblypath": "RemoteFile.dll", "TypeFullName": "Keyfactor.Extensions.Orchestrator.RemoteFile.Discovery" @@ -21,6 +25,10 @@ "assemblypath": "RemoteFile.dll", "TypeFullName": "Keyfactor.Extensions.Orchestrator.RemoteFile.PEM.Management" }, + "CertStores.RFPEM.Reenrollment": { + "assemblypath": "RemoteFile.dll", + "TypeFullName": "Keyfactor.Extensions.Orchestrator.RemoteFile.PEM.Reenrollment" + }, "CertStores.RFPEM.Discovery": { "assemblypath": "RemoteFile.dll", "TypeFullName": "Keyfactor.Extensions.Orchestrator.RemoteFile.Discovery" @@ -33,6 +41,10 @@ "assemblypath": "RemoteFile.dll", "TypeFullName": "Keyfactor.Extensions.Orchestrator.RemoteFile.JKS.Management" }, + "CertStores.RFJKS.Reenrollment": { + "assemblypath": "RemoteFile.dll", + "TypeFullName": "Keyfactor.Extensions.Orchestrator.RemoteFile.JKS.Reenrollment" + }, "CertStores.RFJKS.Discovery": { "assemblypath": "RemoteFile.dll", "TypeFullName": "Keyfactor.Extensions.Orchestrator.RemoteFile.Discovery" @@ -45,6 +57,10 @@ "assemblypath": "RemoteFile.dll", "TypeFullName": "Keyfactor.Extensions.Orchestrator.RemoteFile.KDB.Management" }, + "CertStores.RFKDB.Reenrollment": { + "assemblypath": "RemoteFile.dll", + "TypeFullName": "Keyfactor.Extensions.Orchestrator.RemoteFile.KDB.Reenrollment" + }, "CertStores.RFKDB.Discovery": { "assemblypath": "RemoteFile.dll", "TypeFullName": "Keyfactor.Extensions.Orchestrator.RemoteFile.Discovery" @@ -57,6 +73,10 @@ "assemblypath": "RemoteFile.dll", "TypeFullName": "Keyfactor.Extensions.Orchestrator.RemoteFile.DER.Management" }, + "CertStores.RFDER.Reenrollment": { + "assemblypath": "RemoteFile.dll", + "TypeFullName": "Keyfactor.Extensions.Orchestrator.RemoteFile.DER.Reenrollment" + }, "CertStores.RFDER.Discovery": { "assemblypath": "RemoteFile.dll", "TypeFullName": "Keyfactor.Extensions.Orchestrator.RemoteFile.Discovery" @@ -69,6 +89,10 @@ "assemblypath": "RemoteFile.dll", "TypeFullName": "Keyfactor.Extensions.Orchestrator.RemoteFile.OraWlt.Management" }, + "CertStores.RFORA.Reenrollment": { + "assemblypath": "RemoteFile.dll", + "TypeFullName": "Keyfactor.Extensions.Orchestrator.RemoteFile.OraWlt.Reenrollment" + }, "CertStores.RFORA.Discovery": { "assemblypath": "RemoteFile.dll", "TypeFullName": "Keyfactor.Extensions.Orchestrator.RemoteFile.Discovery" From f4a56ddf1f7fc8685247ba51b28514b9c12a7e96 Mon Sep 17 00:00:00 2001 From: leefine02 Date: Wed, 3 Jul 2024 15:17:04 +0000 Subject: [PATCH 12/20] ab#59979 --- RemoteFile/ApplicationSettings.cs | 1 + RemoteFile/ReenrollmentBase.cs | 139 ++++----------------------- RemoteFile/RemoteCertificateStore.cs | 80 +++++++++++++++ 3 files changed, 101 insertions(+), 119 deletions(-) diff --git a/RemoteFile/ApplicationSettings.cs b/RemoteFile/ApplicationSettings.cs index 790ad4d..ee3caf7 100644 --- a/RemoteFile/ApplicationSettings.cs +++ b/RemoteFile/ApplicationSettings.cs @@ -39,6 +39,7 @@ public enum FileTransferProtocolEnum public static string DefaultOwnerOnStoreCreation { get { return configuration.ContainsKey("DefaultOwnerOnStoreCreation") ? configuration["DefaultOwnerOnStoreCreation"] : DEFAULT_OWNER_SETTING; } } public static string DefaultSudoImpersonatedUser { get { return configuration.ContainsKey("DefaultSudoImpersonatedUser") ? configuration["DefaultSudoImpersonatedUser"] : DEFAULT_SUDO_IMPERSONATION_SETTING; } } public static bool CreateCSROnDevice { get { return configuration.ContainsKey("CreateCSROnDevice") ? configuration["CreateCSROnDevice"]?.ToUpper() == "Y" : false; } } + public static string TempFilePathForODKG { get { return configuration.ContainsKey("TempFilePathForODKG") ? configuration["TempFilePathForODKG"] : string.Empty; } } public static FileTransferProtocolEnum FileTransferProtocol { get diff --git a/RemoteFile/ReenrollmentBase.cs b/RemoteFile/ReenrollmentBase.cs index 4de5e67..7708d6c 100644 --- a/RemoteFile/ReenrollmentBase.cs +++ b/RemoteFile/ReenrollmentBase.cs @@ -16,7 +16,6 @@ using Microsoft.Extensions.Logging; using Newtonsoft.Json; -using static Org.BouncyCastle.Math.EC.ECCurve; using Org.BouncyCastle.Asn1.Pkcs; using Org.BouncyCastle.Asn1.X509; using Org.BouncyCastle.Asn1; @@ -24,8 +23,7 @@ using Org.BouncyCastle.Crypto.Prng; using Org.BouncyCastle.Crypto; using Org.BouncyCastle.Pkcs; -using System.Linq; -using System.Security.Cryptography.Pkcs; +using System.Security.Cryptography; namespace Keyfactor.Extensions.Orchestrator.RemoteFile { @@ -53,7 +51,7 @@ public JobResult ProcessJob(ReenrollmentJobConfiguration config, SubmitReenrollm { logger.LogDebug($" {keyValue.Key}: {keyValue.Value}"); } - + ICertificateStoreSerializer certificateStoreSerializer = GetCertificateStoreSerializer(config.CertificateStoreDetails.Properties); try @@ -87,31 +85,38 @@ public JobResult ProcessJob(ReenrollmentJobConfiguration config, SubmitReenrollm throw new RemoteFileException($"Unsupported KeyType value {keyType}. Supported types are {keyTypes}."); } + certificateStore = new RemoteCertificateStore(config.CertificateStoreDetails.ClientMachine, userName, userPassword, config.CertificateStoreDetails.StorePath, storePassword, config.JobProperties); + certificateStore.Initialize(sudoImpersonatedUser); + + PathFile storePathFile = RemoteCertificateStore.SplitStorePathFile(config.CertificateStoreDetails.StorePath); + + if (!certificateStore.DoesStoreExist()) + { + throw new RemoteFileException($"Certificate store {config.CertificateStoreDetails.StorePath} does not exist on server {config.CertificateStoreDetails.ClientMachine}."); + } + // generate CSR and call back to enroll certificate string csr = string.Empty; + string pemPrivateKey = string.Empty; if (createCSROnDevice) { - throw new Exception("Not implemented"); + csr = certificateStore.GenerateCSROnDevice(subjectText, keyTypeEnum, keySize, new List(sans.Split('&', StringSplitOptions.RemoveEmptyEntries)), out pemPrivateKey); } else { - csr = GenerateCSR(subjectText, keyTypeEnum, keySize, new List(sans.Split('&', StringSplitOptions.RemoveEmptyEntries))); + csr = certificateStore.GenerateCSR(subjectText, keyTypeEnum, keySize, new List(sans.Split('&', StringSplitOptions.RemoveEmptyEntries))); } X509Certificate2 cert = submitReenrollment.Invoke(csr); - - // save certificate - certificateStore = new RemoteCertificateStore(config.CertificateStoreDetails.ClientMachine, userName, userPassword, config.CertificateStoreDetails.StorePath, storePassword, config.JobProperties); - certificateStore.Initialize(sudoImpersonatedUser); - - PathFile storePathFile = RemoteCertificateStore.SplitStorePathFile(config.CertificateStoreDetails.StorePath); - - if (!certificateStore.DoesStoreExist()) + if (!string.IsNullOrEmpty(pemPrivateKey)) { - throw new RemoteFileException($"Certificate store {config.CertificateStoreDetails.StorePath} does not exist on server {config.CertificateStoreDetails.ClientMachine}."); + RSA rsa = RSA.Create(); + rsa.ImportEncryptedPkcs8PrivateKey(string.Empty, Convert.FromBase64String(pemPrivateKey), out _); + cert = cert.CopyWithPrivateKey(rsa); } + // save certificate certificateStore.LoadCertificateStore(certificateStoreSerializer, config.CertificateStoreDetails.Properties, false); certificateStore.AddCertificate((alias ?? cert.Thumbprint), Convert.ToBase64String(cert.Export(X509ContentType.Cert)), overwrite, null); certificateStore.SaveCertificateStore(certificateStoreSerializer.SerializeRemoteCertificateStore(certificateStore.GetCertificateStore(), storePathFile.Path, storePathFile.File, storePassword, certificateStore.RemoteHandler)); @@ -133,108 +138,4 @@ public JobResult ProcessJob(ReenrollmentJobConfiguration config, SubmitReenrollm logger.LogDebug($"...End {config.Capability} job for job id {config.JobId}"); return new JobResult() { Result = OrchestratorJobStatusJobResult.Success, JobHistoryId = config.JobHistoryId }; } - - internal string GenerateCSR(string subjectText, SupportedKeyTypeEnum keyType, int keySize, List sans) - { - IAsymmetricCipherKeyPairGenerator keyPairGenerator = null; - string algorithm = string.Empty; - switch (keyType) - { - case SupportedKeyTypeEnum.RSA: - keyPairGenerator = new RsaKeyPairGenerator(); - algorithm = "SHA256withRSA"; - break; - case SupportedKeyTypeEnum.ECC: - keyPairGenerator = new ECKeyPairGenerator(); - algorithm = "SHA256withECDSA"; - if (keySize == 384) algorithm = "SHA384withECDSA"; - if (keySize == 521) algorithm = "SHA512withECDSA"; - break; - } - - var keyGenParams = new KeyGenerationParameters(new Org.BouncyCastle.Security.SecureRandom(new CryptoApiRandomGenerator()), keySize); - keyPairGenerator.Init(keyGenParams); - var keyPair = keyPairGenerator.GenerateKeyPair(); - - var subject = new X509Name(subjectText); - - // Add SAN entries - var subAltNameList = new List(); - sans.ForEach(san => subAltNameList.Add(new GeneralName(GeneralName.DnsName, san.Trim()))); - var generalSubAltNames = new GeneralNames(subAltNameList.ToArray()); - - var extensionsGenerator = new X509ExtensionsGenerator(); - extensionsGenerator.AddExtension(X509Extensions.SubjectAlternativeName, false, generalSubAltNames); - var attributeSet = new DerSet(new AttributePkcs(PkcsObjectIdentifiers.Pkcs9AtExtensionRequest, new DerSet(extensionsGenerator.Generate()))); - - Pkcs10CertificationRequest csr = new Pkcs10CertificationRequest(algorithm, subject, keyPair.Public, (DerSet)attributeSet, keyPair.Private); - - // encode the CSR as base64 - var encodedCsr = Convert.ToBase64String(csr.GetEncoded()); - return encodedCsr; - } - - - //private string GenerateCSR(string subjectText, SupportedKeyTypeEnum keyType, int keySize, List sans) - //{ - // //Code logic to: - // // 1) Generate a new CSR - // // 2) Include the provided subject text - // // 3) Include the list of SANs - // // 3) Include the OID corresponding to a Time Stamping request, so Command recognizes this as a request for re-enrollment - // // 4) Return the base64 encoded CSR. - - // // this approach relies on the Bouncy Castle Crypto package, and not the Microsoft x509 certificate libraries. - - // IAsymmetricCipherKeyPairGenerator keyPairGenerator = null; - // switch (keyType) - // { - // case SupportedKeyTypeEnum.RSA: - // keyPairGenerator = new RsaKeyPairGenerator(); - // break; - // case SupportedKeyTypeEnum.ECC: - // keyPairGenerator = new ECKeyPairGenerator(); - // break; - // } - - // var keyGenParams = new KeyGenerationParameters(new Org.BouncyCastle.Security.SecureRandom(new CryptoApiRandomGenerator()), keySize); - // keyPairGenerator.Init(keyGenParams); - - // var keyPair = keyPairGenerator.GenerateKeyPair(); - // var subject = new X509Name(subjectText); - - // // Add SAN entries - // var subAltNameList = new List(); - // sans.ForEach(san => subAltNameList.Add(new GeneralName(GeneralName.DnsName, san.Trim()))); - // var generalSubAltNames = new GeneralNames(subAltNameList.ToArray()); - - // // Create Key Usage attribute - // //int keyUsage = KeyUsage.DigitalSignature | KeyUsage.NonRepudiation; - // //var keyUsageExtension = new KeyUsage(keyUsage); - - // // Add Extended Key Usage extension for re-enrollment (1.3.6.1.5.5.7.3.8 is the OID for time stamping, the Command CA should be configured to recognize a CSR with this OID as a request for re-enrollment) - // //var timestampOid = new DerObjectIdentifier("1.3.6.1.5.5.7.3.8"); // https://oidref.com/1.3.6.1.5.5.7.3.8 - // //var extendedKeyUsage = new ExtendedKeyUsage(new DerObjectIdentifier[] { timestampOid }); - - // // Create extensions - // var extensionsGenerator = new X509ExtensionsGenerator(); - // extensionsGenerator.AddExtension(X509Extensions.SubjectAlternativeName, false, generalSubAltNames); - // //extensionsGenerator.AddExtension(X509Extensions.KeyUsage, true, keyUsageExtension); - // //extensionsGenerator.AddExtension(X509Extensions.ExtendedKeyUsage, false, extendedKeyUsage); - // X509Extensions extensions = extensionsGenerator.Generate(); - - // // Create attribute set with extensions - // //var attributeSet = new AttributePkcs(PkcsObjectIdentifiers.Pkcs9AtExtensionRequest, new DerSet(extensions)); - // var attribute = new DerSet(new AttributePkcs(PkcsObjectIdentifiers.Pkcs9AtExtensionRequest, new DerSet(extensions))); - // var attributeSet = new DerSet(new AttributePkcs(PkcsObjectIdentifiers.Pkcs9AtExtensionRequest, new DerSet(extensionsGenerator.Generate()))); - - // // Include the attributes in the request - // var csr = new Pkcs10CertificationRequest(PkcsObjectIdentifiers.Pkcs9AtExtensionRequest.Id, subject, keyPair.Public, attributeSet, keyPair.Private); - // //var csr = new Pkcs10CertificationRequest(PkcsObjectIdentifiers.Pkcs9AtExtensionRequest.Id, subject, keyPair.Public, new DerSet(attributeSet), keyPair.Private); - - // // encode the CSR as base64 - // var encodedCsr = Convert.ToBase64String(csr.GetEncoded()); - // return encodedCsr; - //} } -} diff --git a/RemoteFile/RemoteCertificateStore.cs b/RemoteFile/RemoteCertificateStore.cs index 104b671..0ea6113 100644 --- a/RemoteFile/RemoteCertificateStore.cs +++ b/RemoteFile/RemoteCertificateStore.cs @@ -22,6 +22,13 @@ using Keyfactor.Extensions.Orchestrator.RemoteFile.Models; using Keyfactor.Logging; using System.Runtime.InteropServices; +using Org.BouncyCastle.Asn1.Pkcs; +using Org.BouncyCastle.Asn1.X509; +using Org.BouncyCastle.Asn1; +using Org.BouncyCastle.Crypto.Generators; +using Org.BouncyCastle.Crypto.Prng; +using Org.BouncyCastle.Crypto; +using static Keyfactor.Extensions.Orchestrator.RemoteFile.ReenrollmentBase; namespace Keyfactor.Extensions.Orchestrator.RemoteFile { @@ -336,6 +343,79 @@ internal static PathFile SplitStorePathFile(string pathFileName) } } + internal string GenerateCSR(string subjectText, SupportedKeyTypeEnum keyType, int keySize, List sans) + { + IAsymmetricCipherKeyPairGenerator keyPairGenerator = null; + string algorithm = string.Empty; + switch (keyType) + { + case SupportedKeyTypeEnum.RSA: + keyPairGenerator = new RsaKeyPairGenerator(); + algorithm = "SHA256withRSA"; + break; + case SupportedKeyTypeEnum.ECC: + keyPairGenerator = new ECKeyPairGenerator(); + algorithm = "SHA256withECDSA"; + if (keySize == 384) algorithm = "SHA384withECDSA"; + if (keySize == 521) algorithm = "SHA512withECDSA"; + break; + } + + var keyGenParams = new KeyGenerationParameters(new Org.BouncyCastle.Security.SecureRandom(new CryptoApiRandomGenerator()), keySize); + keyPairGenerator.Init(keyGenParams); + var keyPair = keyPairGenerator.GenerateKeyPair(); + + var subject = new X509Name(subjectText); + + // Add SAN entries + var subAltNameList = new List(); + sans.ForEach(san => subAltNameList.Add(new GeneralName(GeneralName.DnsName, san.Trim()))); + var generalSubAltNames = new GeneralNames(subAltNameList.ToArray()); + + var extensionsGenerator = new X509ExtensionsGenerator(); + extensionsGenerator.AddExtension(X509Extensions.SubjectAlternativeName, false, generalSubAltNames); + var attributeSet = new DerSet(new AttributePkcs(PkcsObjectIdentifiers.Pkcs9AtExtensionRequest, new DerSet(extensionsGenerator.Generate()))); + + Pkcs10CertificationRequest csr = new Pkcs10CertificationRequest(algorithm, subject, keyPair.Public, (DerSet)attributeSet, keyPair.Private); + + // encode the CSR as base64 + var encodedCsr = Convert.ToBase64String(csr.GetEncoded()); + return encodedCsr; + } + + internal string GenerateCSROnDevice(string subjectText, SupportedKeyTypeEnum keyType, int keySize, List sans, out string privateKey) + { + string path = ApplicationSettings.TempFilePathForODKG; + if (path.Substring(path.Length - 1, 1) != "/") path += "/"; + string fileName = Guid.NewGuid().ToString(); + + X500DistinguishedName dn = new X500DistinguishedName(subjectText); + string opensslSubject = dn.Format(true); + opensslSubject = "/" + opensslSubject.Replace(System.Environment.NewLine, "/").Substring(0, opensslSubject.Length - 1); + + string cmd = $"openssl req -new -newkey REPLACE -nodes -keyout {path}{fileName}.key -out {{path}}{{fileName}}.csr -subj '{opensslSubject}'"; + switch (keyType) + { + case SupportedKeyTypeEnum.RSA: + cmd.Replace("REPLACE", $"rsa:{keySize.ToString()})"; + break; + case SupportedKeyTypeEnum.ECC: + string algName = "prime256v1"; + switch (keySize) + { + case 384: + algName = "secp384r1"; + break; + case 521: + algName = "secp521r1"; + break; + } + cmd.Replace("REPLACE", $"ec:<(openssl ecparam -name {algName})"); + break; + } + + } + internal void Initialize(string sudoImpersonatedUser) { logger.MethodEntry(LogLevel.Debug); From 06f9cd66da59f683b186c1e42e8a44671992e555 Mon Sep 17 00:00:00 2001 From: leefine02 Date: Wed, 3 Jul 2024 18:54:11 +0000 Subject: [PATCH 13/20] ab#59979 --- RemoteFile/ReenrollmentBase.cs | 3 ++- RemoteFile/RemoteCertificateStore.cs | 5 ++++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/RemoteFile/ReenrollmentBase.cs b/RemoteFile/ReenrollmentBase.cs index 7708d6c..e748a2f 100644 --- a/RemoteFile/ReenrollmentBase.cs +++ b/RemoteFile/ReenrollmentBase.cs @@ -51,7 +51,7 @@ public JobResult ProcessJob(ReenrollmentJobConfiguration config, SubmitReenrollm { logger.LogDebug($" {keyValue.Key}: {keyValue.Value}"); } - + ICertificateStoreSerializer certificateStoreSerializer = GetCertificateStoreSerializer(config.CertificateStoreDetails.Properties); try @@ -139,3 +139,4 @@ public JobResult ProcessJob(ReenrollmentJobConfiguration config, SubmitReenrollm return new JobResult() { Result = OrchestratorJobStatusJobResult.Success, JobHistoryId = config.JobHistoryId }; } } +} diff --git a/RemoteFile/RemoteCertificateStore.cs b/RemoteFile/RemoteCertificateStore.cs index 0ea6113..c608bf6 100644 --- a/RemoteFile/RemoteCertificateStore.cs +++ b/RemoteFile/RemoteCertificateStore.cs @@ -397,7 +397,7 @@ internal string GenerateCSROnDevice(string subjectText, SupportedKeyTypeEnum key switch (keyType) { case SupportedKeyTypeEnum.RSA: - cmd.Replace("REPLACE", $"rsa:{keySize.ToString()})"; + cmd.Replace("REPLACE", $"rsa:{keySize.ToString()}"); break; case SupportedKeyTypeEnum.ECC: string algName = "prime256v1"; @@ -414,6 +414,9 @@ internal string GenerateCSROnDevice(string subjectText, SupportedKeyTypeEnum key break; } + RemoteHandler.RunCommand(cmd, null, ApplicationSettings.UseSudo, null); + privateKey = Encoding.UTF8.GetString(RemoteHandler.DownloadCertificateFile(path + fileName + "key")); + return Encoding.UTF8.GetString(RemoteHandler.DownloadCertificateFile(path + fileName + "csr")); } internal void Initialize(string sudoImpersonatedUser) From c1f1b20a9e8c98f15f501030669c873f65c44cb0 Mon Sep 17 00:00:00 2001 From: leefine02 Date: Wed, 3 Jul 2024 20:45:53 +0000 Subject: [PATCH 14/20] ab#59979 --- RemoteFile/RemoteCertificateStore.cs | 32 ++++++++++++++++++++++------ 1 file changed, 25 insertions(+), 7 deletions(-) diff --git a/RemoteFile/RemoteCertificateStore.cs b/RemoteFile/RemoteCertificateStore.cs index c608bf6..301a4bf 100644 --- a/RemoteFile/RemoteCertificateStore.cs +++ b/RemoteFile/RemoteCertificateStore.cs @@ -391,13 +391,14 @@ internal string GenerateCSROnDevice(string subjectText, SupportedKeyTypeEnum key X500DistinguishedName dn = new X500DistinguishedName(subjectText); string opensslSubject = dn.Format(true); - opensslSubject = "/" + opensslSubject.Replace(System.Environment.NewLine, "/").Substring(0, opensslSubject.Length - 1); + opensslSubject = opensslSubject.Replace(System.Environment.NewLine, "/"); + opensslSubject = "/" + opensslSubject.Substring(0, opensslSubject.Length - 1); - string cmd = $"openssl req -new -newkey REPLACE -nodes -keyout {path}{fileName}.key -out {{path}}{{fileName}}.csr -subj '{opensslSubject}'"; + string cmd = $"openssl req -new -newkey REPLACE -nodes -keyout {path}{fileName}.key -out {path}{fileName}.csr -subj '{opensslSubject}'"; switch (keyType) { case SupportedKeyTypeEnum.RSA: - cmd.Replace("REPLACE", $"rsa:{keySize.ToString()}"); + cmd = cmd.Replace("REPLACE", $"rsa:{keySize.ToString()}"); break; case SupportedKeyTypeEnum.ECC: string algName = "prime256v1"; @@ -410,13 +411,30 @@ internal string GenerateCSROnDevice(string subjectText, SupportedKeyTypeEnum key algName = "secp521r1"; break; } - cmd.Replace("REPLACE", $"ec:<(openssl ecparam -name {algName})"); + cmd = cmd.Replace("REPLACE", $"ec:<(openssl ecparam -name {algName})"); break; } - RemoteHandler.RunCommand(cmd, null, ApplicationSettings.UseSudo, null); - privateKey = Encoding.UTF8.GetString(RemoteHandler.DownloadCertificateFile(path + fileName + "key")); - return Encoding.UTF8.GetString(RemoteHandler.DownloadCertificateFile(path + fileName + "csr")); + string csr = string.Empty; + privateKey = string.Empty; + try + { + RemoteHandler.RunCommand(cmd, null, ApplicationSettings.UseSudo, null); + privateKey = Encoding.UTF8.GetString(RemoteHandler.DownloadCertificateFile(path + fileName + "key")); + csr = Encoding.UTF8.GetString(RemoteHandler.DownloadCertificateFile(path + fileName + "csr")); + } + catch (Exception ex) + { + if (!ex.Message.Contains("----") || !ex.Message.Contains("++++")) + throw; + } + finally + { + RemoteHandler.RemoveCertificateFile(path, fileName + "key"); + RemoteHandler.RemoveCertificateFile(path, fileName + "csr"); + } + + return csr; } internal void Initialize(string sudoImpersonatedUser) From e0cbf975c4f815bbadc9dfc56f90b1836450b1f8 Mon Sep 17 00:00:00 2001 From: leefine02 Date: Fri, 5 Jul 2024 19:25:21 +0000 Subject: [PATCH 15/20] ab#59979 --- RemoteFile/ReenrollmentBase.cs | 33 +++++++++++++++++----------- RemoteFile/RemoteCertificateStore.cs | 28 +++++++++++++---------- 2 files changed, 37 insertions(+), 24 deletions(-) diff --git a/RemoteFile/ReenrollmentBase.cs b/RemoteFile/ReenrollmentBase.cs index e748a2f..9f8fafa 100644 --- a/RemoteFile/ReenrollmentBase.cs +++ b/RemoteFile/ReenrollmentBase.cs @@ -12,17 +12,11 @@ using Keyfactor.Logging; using Keyfactor.Orchestrators.Extensions; using Keyfactor.Orchestrators.Common.Enums; +using Keyfactor.PKI.PEM; using Microsoft.Extensions.Logging; using Newtonsoft.Json; -using Org.BouncyCastle.Asn1.Pkcs; -using Org.BouncyCastle.Asn1.X509; -using Org.BouncyCastle.Asn1; -using Org.BouncyCastle.Crypto.Generators; -using Org.BouncyCastle.Crypto.Prng; -using Org.BouncyCastle.Crypto; -using Org.BouncyCastle.Pkcs; using System.Security.Cryptography; namespace Keyfactor.Extensions.Orchestrator.RemoteFile @@ -108,17 +102,29 @@ public JobResult ProcessJob(ReenrollmentJobConfiguration config, SubmitReenrollm } X509Certificate2 cert = submitReenrollment.Invoke(csr); + if (cert == null) + throw new RemoteFileException("Enrollment of CSR failed. Please check Keyfactor Command logs for more information on potential enrollment errors."); if (!string.IsNullOrEmpty(pemPrivateKey)) { - RSA rsa = RSA.Create(); - rsa.ImportEncryptedPkcs8PrivateKey(string.Empty, Convert.FromBase64String(pemPrivateKey), out _); - cert = cert.CopyWithPrivateKey(rsa); + if (keyTypeEnum == SupportedKeyTypeEnum.RSA) + { + RSA rsa = RSA.Create(); + rsa.ImportEncryptedPkcs8PrivateKey(string.Empty, Keyfactor.PKI.PEM.PemUtilities.PEMToDER(pemPrivateKey), out _); + cert = cert.CopyWithPrivateKey(rsa); + } + else + { + ECCurve ec = ECCurve.CreateFromValue("1.3.132.0.34"); + ECDsa e = ECDsa.Create(ec); + e.ImportECPrivateKey(Keyfactor.PKI.PEM.PemUtilities.PEMToDER(pemPrivateKey), out _); + cert = cert.CopyWithPrivateKey(e); + } } // save certificate certificateStore.LoadCertificateStore(certificateStoreSerializer, config.CertificateStoreDetails.Properties, false); - certificateStore.AddCertificate((alias ?? cert.Thumbprint), Convert.ToBase64String(cert.Export(X509ContentType.Cert)), overwrite, null); + certificateStore.AddCertificate((alias ?? cert.Thumbprint), Convert.ToBase64String(cert.Export(X509ContentType.Pfx)), overwrite, null); certificateStore.SaveCertificateStore(certificateStoreSerializer.SerializeRemoteCertificateStore(certificateStore.GetCertificateStore(), storePathFile.Path, storePathFile.File, storePassword, certificateStore.RemoteHandler)); logger.LogDebug($"END add Operation for {config.CertificateStoreDetails.StorePath} on {config.CertificateStoreDetails.ClientMachine}."); @@ -126,8 +132,9 @@ public JobResult ProcessJob(ReenrollmentJobConfiguration config, SubmitReenrollm catch (Exception ex) { - logger.LogError($"Exception for {config.Capability}: {RemoteFileException.FlattenExceptionMessages(ex, string.Empty)} for job id {config.JobId}"); - return new JobResult() { Result = OrchestratorJobStatusJobResult.Failure, JobHistoryId = config.JobHistoryId, FailureMessage = RemoteFileException.FlattenExceptionMessages(ex, $"Site {config.CertificateStoreDetails.StorePath} on server {config.CertificateStoreDetails.ClientMachine}:") }; + string errorMessage = $"Exception for {config.Capability}: {RemoteFileException.FlattenExceptionMessages(ex, string.Empty)} for job id {config.JobId}"; + logger.LogError(errorMessage); + return new JobResult() { Result = OrchestratorJobStatusJobResult.Failure, JobHistoryId = config.JobHistoryId, FailureMessage = $"Site {config.CertificateStoreDetails.StorePath} on server {config.CertificateStoreDetails.ClientMachine}: {errorMessage}" }; } finally { diff --git a/RemoteFile/RemoteCertificateStore.cs b/RemoteFile/RemoteCertificateStore.cs index 301a4bf..3a8dd18 100644 --- a/RemoteFile/RemoteCertificateStore.cs +++ b/RemoteFile/RemoteCertificateStore.cs @@ -390,7 +390,7 @@ internal string GenerateCSROnDevice(string subjectText, SupportedKeyTypeEnum key string fileName = Guid.NewGuid().ToString(); X500DistinguishedName dn = new X500DistinguishedName(subjectText); - string opensslSubject = dn.Format(true); + string opensslSubject = dn.Format(true).Replace("S=","ST="); opensslSubject = opensslSubject.Replace(System.Environment.NewLine, "/"); opensslSubject = "/" + opensslSubject.Substring(0, opensslSubject.Length - 1); @@ -419,19 +419,25 @@ internal string GenerateCSROnDevice(string subjectText, SupportedKeyTypeEnum key privateKey = string.Empty; try { - RemoteHandler.RunCommand(cmd, null, ApplicationSettings.UseSudo, null); - privateKey = Encoding.UTF8.GetString(RemoteHandler.DownloadCertificateFile(path + fileName + "key")); - csr = Encoding.UTF8.GetString(RemoteHandler.DownloadCertificateFile(path + fileName + "csr")); - } - catch (Exception ex) - { - if (!ex.Message.Contains("----") || !ex.Message.Contains("++++")) - throw; + try + { + RemoteHandler.RunCommand(cmd, null, ApplicationSettings.UseSudo, null); + } + catch (Exception ex) + { + if (!ex.Message.Contains("----")) + throw; + } + + privateKey = Encoding.UTF8.GetString(RemoteHandler.DownloadCertificateFile(path + fileName + ".key")); + csr = Encoding.UTF8.GetString(RemoteHandler.DownloadCertificateFile(path + fileName + ".csr")); } finally { - RemoteHandler.RemoveCertificateFile(path, fileName + "key"); - RemoteHandler.RemoveCertificateFile(path, fileName + "csr"); + if (RemoteHandler.DoesFileExist(path + fileName + ".key")) + RemoteHandler.RemoveCertificateFile(path, fileName + ".key"); + if (RemoteHandler.DoesFileExist(path + fileName + ".csr")) + RemoteHandler.RemoveCertificateFile(path, fileName + ".csr"); } return csr; From 6ecd9a58b1c8bc86231d777d70420ae47f6c0be8 Mon Sep 17 00:00:00 2001 From: leefine02 Date: Mon, 8 Jul 2024 13:40:07 +0000 Subject: [PATCH 16/20] ab#59979 --- RemoteFile/ReenrollmentBase.cs | 35 +++++++++++++++------------------- RemoteFile/config.json | 1 - RemoteFile/manifest.json | 24 ----------------------- 3 files changed, 15 insertions(+), 45 deletions(-) diff --git a/RemoteFile/ReenrollmentBase.cs b/RemoteFile/ReenrollmentBase.cs index 9f8fafa..ed7ca86 100644 --- a/RemoteFile/ReenrollmentBase.cs +++ b/RemoteFile/ReenrollmentBase.cs @@ -21,7 +21,7 @@ namespace Keyfactor.Extensions.Orchestrator.RemoteFile { - public abstract class ReenrollmentBase : RemoteFileJobTypeBase, IReenrollmentJobExtension + public abstract class ReenrollmentBase : RemoteFileJobTypeBase { public string ExtensionName => "Keyfactor.Extensions.Orchestrator.RemoteFile"; @@ -33,7 +33,16 @@ internal enum SupportedKeyTypeEnum ECC } - public JobResult ProcessJob(ReenrollmentJobConfiguration config, SubmitReenrollmentCSR submitReenrollment) + //TODO: + // 1) Set SANs, Alias and Overwrite "for real" once product figures out how to pass that + // 2) Add "CreateCSROnDevice" (Y/N) to config.json + // 3) Add "TempFilePathForODKG" (string) to config.json + // 4) Add Reenrollment to manifest.json for all store types + // 5) Rename ProcessJobToDo to ProcessJob + // 6) Modify ReenrollmentBase to implement IReenrollmentJobExtension + // 6) Update README. Remember to explain the differences between ODKG and OOKG + + public JobResult ProcessJobToDo(ReenrollmentJobConfiguration config, SubmitReenrollmentCSR submitReenrollment) { ILogger logger = LogHandler.GetClassLogger(this.GetType()); logger.LogDebug($"Begin {config.Capability} for job id {config.JobId}..."); @@ -67,7 +76,6 @@ public JobResult ProcessJob(ReenrollmentJobConfiguration config, SubmitReenrollm int keySize = !config.JobProperties.ContainsKey("keySize") || config.JobProperties["keySize"] == null || string.IsNullOrEmpty(config.JobProperties["keySize"].ToString()) ? 2048 : Convert.ToInt32(config.JobProperties["keySize"]); string subjectText = !config.JobProperties.ContainsKey("subjectText") || config.JobProperties["subjectText"] == null || config.JobProperties["subjectText"] == null || string.IsNullOrEmpty(config.JobProperties["subjectText"].ToString()) ? string.Empty : config.JobProperties["subjectText"].ToString(); - //TODO - Set SANs, Alias and Overwrite "for real" once product figures out how to pass that string alias = "abcd"; string sans = "reenroll2.Keyfactor.com&reenroll1.keyfactor.com&reenroll3.Keyfactor.com"; bool overwrite = true; @@ -102,25 +110,12 @@ public JobResult ProcessJob(ReenrollmentJobConfiguration config, SubmitReenrollm } X509Certificate2 cert = submitReenrollment.Invoke(csr); - if (cert == null) + if (cert == null || String.IsNullOrEmpty(pemPrivateKey)) throw new RemoteFileException("Enrollment of CSR failed. Please check Keyfactor Command logs for more information on potential enrollment errors."); - if (!string.IsNullOrEmpty(pemPrivateKey)) - { - if (keyTypeEnum == SupportedKeyTypeEnum.RSA) - { - RSA rsa = RSA.Create(); - rsa.ImportEncryptedPkcs8PrivateKey(string.Empty, Keyfactor.PKI.PEM.PemUtilities.PEMToDER(pemPrivateKey), out _); - cert = cert.CopyWithPrivateKey(rsa); - } - else - { - ECCurve ec = ECCurve.CreateFromValue("1.3.132.0.34"); - ECDsa e = ECDsa.Create(ec); - e.ImportECPrivateKey(Keyfactor.PKI.PEM.PemUtilities.PEMToDER(pemPrivateKey), out _); - cert = cert.CopyWithPrivateKey(e); - } - } + AsymmetricAlgorithm alg = keyTypeEnum == SupportedKeyTypeEnum.RSA ? RSA.Create() : ECDsa.Create(); + alg.ImportEncryptedPkcs8PrivateKey(string.Empty, Keyfactor.PKI.PEM.PemUtilities.PEMToDER(pemPrivateKey), out _); + cert = keyTypeEnum == SupportedKeyTypeEnum.RSA ? cert.CopyWithPrivateKey((RSA)alg) : cert.CopyWithPrivateKey((ECDsa)alg); // save certificate certificateStore.LoadCertificateStore(certificateStoreSerializer, config.CertificateStoreDetails.Properties, false); diff --git a/RemoteFile/config.json b/RemoteFile/config.json index e24b164..bd69b8a 100644 --- a/RemoteFile/config.json +++ b/RemoteFile/config.json @@ -7,5 +7,4 @@ "FileTransferProtocol": "SCP", "DefaultLinuxPermissionsOnStoreCreation": "600", "DefaultOwnerOnStoreCreation": "", - "CreateCSROnDevice": "N" } \ No newline at end of file diff --git a/RemoteFile/manifest.json b/RemoteFile/manifest.json index 2bf4f13..c4e40bd 100644 --- a/RemoteFile/manifest.json +++ b/RemoteFile/manifest.json @@ -9,10 +9,6 @@ "assemblypath": "RemoteFile.dll", "TypeFullName": "Keyfactor.Extensions.Orchestrator.RemoteFile.PKCS12.Management" }, - "CertStores.RFPkcs12.Reenrollment": { - "assemblypath": "RemoteFile.dll", - "TypeFullName": "Keyfactor.Extensions.Orchestrator.RemoteFile.PKCS12Reenrollment" - }, "CertStores.RFPkcs12.Discovery": { "assemblypath": "RemoteFile.dll", "TypeFullName": "Keyfactor.Extensions.Orchestrator.RemoteFile.Discovery" @@ -25,10 +21,6 @@ "assemblypath": "RemoteFile.dll", "TypeFullName": "Keyfactor.Extensions.Orchestrator.RemoteFile.PEM.Management" }, - "CertStores.RFPEM.Reenrollment": { - "assemblypath": "RemoteFile.dll", - "TypeFullName": "Keyfactor.Extensions.Orchestrator.RemoteFile.PEM.Reenrollment" - }, "CertStores.RFPEM.Discovery": { "assemblypath": "RemoteFile.dll", "TypeFullName": "Keyfactor.Extensions.Orchestrator.RemoteFile.Discovery" @@ -41,10 +33,6 @@ "assemblypath": "RemoteFile.dll", "TypeFullName": "Keyfactor.Extensions.Orchestrator.RemoteFile.JKS.Management" }, - "CertStores.RFJKS.Reenrollment": { - "assemblypath": "RemoteFile.dll", - "TypeFullName": "Keyfactor.Extensions.Orchestrator.RemoteFile.JKS.Reenrollment" - }, "CertStores.RFJKS.Discovery": { "assemblypath": "RemoteFile.dll", "TypeFullName": "Keyfactor.Extensions.Orchestrator.RemoteFile.Discovery" @@ -57,10 +45,6 @@ "assemblypath": "RemoteFile.dll", "TypeFullName": "Keyfactor.Extensions.Orchestrator.RemoteFile.KDB.Management" }, - "CertStores.RFKDB.Reenrollment": { - "assemblypath": "RemoteFile.dll", - "TypeFullName": "Keyfactor.Extensions.Orchestrator.RemoteFile.KDB.Reenrollment" - }, "CertStores.RFKDB.Discovery": { "assemblypath": "RemoteFile.dll", "TypeFullName": "Keyfactor.Extensions.Orchestrator.RemoteFile.Discovery" @@ -73,10 +57,6 @@ "assemblypath": "RemoteFile.dll", "TypeFullName": "Keyfactor.Extensions.Orchestrator.RemoteFile.DER.Management" }, - "CertStores.RFDER.Reenrollment": { - "assemblypath": "RemoteFile.dll", - "TypeFullName": "Keyfactor.Extensions.Orchestrator.RemoteFile.DER.Reenrollment" - }, "CertStores.RFDER.Discovery": { "assemblypath": "RemoteFile.dll", "TypeFullName": "Keyfactor.Extensions.Orchestrator.RemoteFile.Discovery" @@ -89,10 +69,6 @@ "assemblypath": "RemoteFile.dll", "TypeFullName": "Keyfactor.Extensions.Orchestrator.RemoteFile.OraWlt.Management" }, - "CertStores.RFORA.Reenrollment": { - "assemblypath": "RemoteFile.dll", - "TypeFullName": "Keyfactor.Extensions.Orchestrator.RemoteFile.OraWlt.Reenrollment" - }, "CertStores.RFORA.Discovery": { "assemblypath": "RemoteFile.dll", "TypeFullName": "Keyfactor.Extensions.Orchestrator.RemoteFile.Discovery" From 84a7ae232adc89f809cf1b3f6c1e9d8078d9a2c9 Mon Sep 17 00:00:00 2001 From: leefine02 Date: Mon, 8 Jul 2024 13:57:38 +0000 Subject: [PATCH 17/20] ab#59979 --- CHANGELOG.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index c07e7fb..b0d2b19 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,8 @@ +v2.7.0 +- Modified RFJKS store type support java keystores of both PKCS12 and JKS +- Added support for OpenSSH private keys for SSH authentication +- Bug fix for orchestrators installed on Windows 2016 + v2.6.1 - Bug Fix: Supplied Linux user needing password reset could cause orchestrator locking. - Bug Fix: Not supplying group for Linux File Owner on Store Creation caused the supplied owner to erroneously be used as the group for the newly create certificate store file. From abbe0896667813a4940f6ccb142e84f156eb1e3b Mon Sep 17 00:00:00 2001 From: Michael Henderson Date: Mon, 8 Jul 2024 08:01:22 -0700 Subject: [PATCH 18/20] Add support_level setting to integration-manifest.json --- integration-manifest.json | 1 + 1 file changed, 1 insertion(+) diff --git a/integration-manifest.json b/integration-manifest.json index bb423a3..52cb941 100644 --- a/integration-manifest.json +++ b/integration-manifest.json @@ -5,6 +5,7 @@ "status": "production", "link_github": true, "update_catalog": true, + "support_level": "kf-supported", "release_dir": "RemoteFile/bin/Release", "description": "The Remote File Orchestrator allows for the remote management of file-based certificate stores. Discovery, Inventory, and Management functions are supported. The orchestrator performs operations by first converting the certificate store into a BouncyCastle PKCS12Store.", "about": { From 385f0acf5c4b04fc70cc4bcdc9256a2ac54007ee Mon Sep 17 00:00:00 2001 From: Keyfactor Date: Mon, 8 Jul 2024 15:02:07 +0000 Subject: [PATCH 19/20] Update generated README --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index be1d336..1ef294c 100644 --- a/README.md +++ b/README.md @@ -15,7 +15,7 @@ The Universal Orchestrator is the successor to the Windows Orchestrator. This Or ## Support for Remote File -Remote File +Remote File is supported by Keyfactor for Keyfactor customers. If you have a support issue, please open a support ticket via the Keyfactor Support Portal at https://support.keyfactor.com ###### To report a problem or suggest a new feature, use the **[Issues](../../issues)** tab. If you want to contribute actual bug fixes or proposed enhancements, use the **[Pull requests](../../pulls)** tab. From b8119b3fbacf2d1afb6a14571e4d31269d433d62 Mon Sep 17 00:00:00 2001 From: Michael Henderson Date: Tue, 9 Jul 2024 08:33:31 -0700 Subject: [PATCH 20/20] Squash 2.6.1+2.7.0 changelog notes --- CHANGELOG.md | 2 -- 1 file changed, 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b0d2b19..aa79eb0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,8 +2,6 @@ v2.7.0 - Modified RFJKS store type support java keystores of both PKCS12 and JKS - Added support for OpenSSH private keys for SSH authentication - Bug fix for orchestrators installed on Windows 2016 - -v2.6.1 - Bug Fix: Supplied Linux user needing password reset could cause orchestrator locking. - Bug Fix: Not supplying group for Linux File Owner on Store Creation caused the supplied owner to erroneously be used as the group for the newly create certificate store file. - Updgraded Nuget packages for BouncyCastle and Renci.SSH.Net