Skip to content

Commit

Permalink
reverted to .netstandard 2.0 from 2.1 to fix serialization error. Add…
Browse files Browse the repository at this point in the history
… certificate functionality for JKS.
  • Loading branch information
joevanwanzeeleKF committed Oct 19, 2023
1 parent 5da6105 commit b9707fd
Show file tree
Hide file tree
Showing 10 changed files with 147 additions and 80 deletions.
79 changes: 48 additions & 31 deletions hashicorp-vault-orchestrator/FileStores/JksFileStore.cs
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,10 @@ public class JksFileStore : IFileStore
{
internal protected ILogger logger { get; set; }

public JksFileStore()
{
logger = LogHandler.GetClassLogger<JksFileStore>();
}

public string AddCertificate(string alias, string pfxPassword, string entryContents, bool includeChain, string storeFileContent, string passphrase)
{
Expand All @@ -26,7 +30,7 @@ public string AddCertificate(string alias, string pfxPassword, string entryConte
var jksBytes = Convert.FromBase64String(storeFileContent);

logger.LogTrace("converting JKS to PKCS12 store for manipulation");

var newCertBytes = Convert.FromBase64String(entryContents);

logger.LogTrace("adding the new certificate, and getting the new JKS store bytes.");
Expand All @@ -40,11 +44,13 @@ public byte[] CreateFileStore(string name, string password)
// Create new Pkcs12Store from newPkcs12Bytes
var newStore = new JksStore();

using var outstream = new MemoryStream();
logger.LogDebug("Created new JKS store, saving it to outStream");
using (var outstream = new MemoryStream())
{
logger.LogDebug("Created new JKS store, saving it to outStream");

newStore.Save(outstream, password.ToCharArray());
return outstream.ToArray();
newStore.Save(outstream, password.ToCharArray());
return outstream.ToArray();
}
}

public IEnumerable<CurrentInventoryItem> GetInventory(Dictionary<string, object> certFields)
Expand Down Expand Up @@ -120,17 +126,18 @@ private byte[] AddOrRemoveCert(string alias, string newCertPassword, byte[] newC
if (existingStore != null)
{
logger.LogDebug("Loading existing JKS store");
using var ms = new MemoryStream(existingStore);

try
using (var ms = new MemoryStream(existingStore))
{
existingJksStore.Load(ms, string.IsNullOrEmpty(existingStorePassword) ? Array.Empty<char>() : existingStorePassword.ToCharArray());
}
catch (Exception ex)
{
logger.LogError(ex, $"Error loading existing JKS store: {ex.Message}");
}

try
{
existingJksStore.Load(ms, string.IsNullOrEmpty(existingStorePassword) ? Array.Empty<char>() : existingStorePassword.ToCharArray());
}
catch (Exception ex)
{
logger.LogError(ex, $"Error loading existing JKS store: {ex.Message}");
}
}
if (existingJksStore.ContainsAlias(alias))
{
// If alias exists, delete it from existingJksStore
Expand All @@ -140,19 +147,23 @@ private byte[] AddOrRemoveCert(string alias, string newCertPassword, byte[] newC
{
// If remove is true, save existingJksStore and return
logger.LogDebug("This is a removal operation, saving existing JKS store");
using var mms = new MemoryStream();
existingJksStore.Save(mms, string.IsNullOrEmpty(existingStorePassword) ? Array.Empty<char>() : existingStorePassword.ToCharArray());
logger.LogDebug("Returning existing JKS store");
return mms.ToArray();
using (var mms = new MemoryStream())
{
existingJksStore.Save(mms, string.IsNullOrEmpty(existingStorePassword) ? Array.Empty<char>() : existingStorePassword.ToCharArray());
logger.LogDebug("Returning existing JKS store");
return mms.ToArray();
}
}
}
else if (remove)
{
// If alias does not exist and remove is true, return existingStore
logger.LogDebug("Alias '{Alias}' does not exist in existing JKS store and this is a removal operation, returning existing JKS store as-is", alias);
using var mms = new MemoryStream();
existingJksStore.Save(mms, string.IsNullOrEmpty(existingStorePassword) ? Array.Empty<char>() : existingStorePassword.ToCharArray());
return mms.ToArray();
using (var mms = new MemoryStream())
{
existingJksStore.Save(mms, string.IsNullOrEmpty(existingStorePassword) ? Array.Empty<char>() : existingStorePassword.ToCharArray());
return mms.ToArray();
}
}
}
else
Expand All @@ -168,8 +179,10 @@ private byte[] AddOrRemoveCert(string alias, string newCertPassword, byte[] newC
try
{
logger.LogDebug("Loading new Pkcs12Store from newPkcs12Bytes");
using var pkcs12Ms = new MemoryStream(newCertBytes);
newCert.Load(pkcs12Ms, string.IsNullOrEmpty(newCertPassword) ? Array.Empty<char>() : newCertPassword.ToCharArray());
using (var pkcs12Ms = new MemoryStream(newCertBytes))
{
newCert.Load(pkcs12Ms, string.IsNullOrEmpty(newCertPassword) ? Array.Empty<char>() : newCertPassword.ToCharArray());
}
}
catch (Exception)
{
Expand Down Expand Up @@ -237,15 +250,19 @@ private byte[] AddOrRemoveCert(string alias, string newCertPassword, byte[] newC
}
}

using var outStream = new MemoryStream();

logger.LogDebug("Saving existing JKS store to outStream");
existingJksStore.Save(outStream, string.IsNullOrEmpty(existingStorePassword) ? Array.Empty<char>() : existingStorePassword.ToCharArray());

// Return existingJksStore as byte[]
logger.LogDebug("Returning JKS store as byte[]");
return outStream.ToArray();
using (var outStream = new MemoryStream())
{

logger.LogDebug("Saving existing JKS store to outStream");
existingJksStore.Save(outStream, string.IsNullOrEmpty(existingStorePassword) ? Array.Empty<char>() : existingStorePassword.ToCharArray());

// Return existingJksStore as byte[]
logger.LogDebug("Returning JKS store as byte[]");
var bytes = outStream.ToArray();
outStream.Flush();
outStream.Dispose();
return bytes;
}
}

private Pkcs12Store JksToPkcs12Store(byte[] storeContents, string storePassword)
Expand Down
9 changes: 7 additions & 2 deletions hashicorp-vault-orchestrator/FileStores/PfxFileStore.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,11 @@ public class PfxFileStore : IFileStore
{
internal protected ILogger logger { get; set; }

public PfxFileStore()
{
logger = LogHandler.GetClassLogger<PfxFileStore>();
}

public string AddCertificate(string alias, string pfxPassword, string entryContents, bool includeChain, string storeFileContent, string passphrase)
{
throw new NotImplementedException();
Expand All @@ -27,7 +32,7 @@ public IEnumerable<CurrentInventoryItem> GetInventory(Dictionary<string, object>
{
logger = LogHandler.GetClassLogger<PfxFileStore>();
logger.MethodEntry();
// certFields should contain two entries. The certificate with the "pfx-contents" suffix, and "password"
// certFields should contain two entries. The certificate with the "_pfx" suffix, and "passphrase"
string password;
string base64encodedCert;
var certs = new List<CurrentInventoryItem>();
Expand All @@ -44,7 +49,7 @@ public IEnumerable<CurrentInventoryItem> GetInventory(Dictionary<string, object>
base64encodedCert = certFields[certKey].ToString();
}

if (certFields.TryGetValue("password", out object filePasswordObj))
if (certFields.TryGetValue("passphrase", out object filePasswordObj))
{
password = filePasswordObj.ToString();
}
Expand Down
5 changes: 5 additions & 0 deletions hashicorp-vault-orchestrator/FileStores/Pkcs12FileStore.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,11 @@ public class Pkcs12FileStore : IFileStore
{
internal protected ILogger logger { get; set; }

public Pkcs12FileStore()
{
logger = LogHandler.GetClassLogger<Pkcs12FileStore>();
}

public string AddCertificate(string alias, string pfxPassword, string entryContents, bool includeChain, string storeFileContent, string passphrase)
{
throw new NotImplementedException();
Expand Down
65 changes: 44 additions & 21 deletions hashicorp-vault-orchestrator/HcvKeyValueClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,9 @@
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net.Http;
using System.Text;
using System.Text.Json;
using System.Threading.Tasks;
using Keyfactor.Extensions.Orchestrator.HashicorpVault.FileStores;
using Keyfactor.Logging;
Expand All @@ -23,6 +25,7 @@
using VaultSharp.V1.AuthMethods;
using VaultSharp.V1.AuthMethods.Token;
using VaultSharp.V1.Commons;
using VaultSharp.V1.SecretsEngines.KeyValue.V2;

namespace Keyfactor.Extensions.Orchestrator.HashicorpVault
{
Expand All @@ -49,7 +52,7 @@ public HcvKeyValueClient(string vaultToken, string serverUrl, string mountPoint,
// Initialize settings. You can also set proxies, custom delegates etc. here.
var clientSettings = new VaultClientSettings(serverUrl, authMethod);
_mountPoint = mountPoint;
_storePath = !string.IsNullOrEmpty(storePath) ? "/" + storePath.Trim() : storePath?.Trim();
_storePath = (!string.IsNullOrEmpty(storePath) && !storePath.StartsWith("/")) ? "/" + storePath.Trim() : storePath?.Trim();
_vaultClient = new VaultClient(clientSettings);
_subfolderInventory = SubfolderInventory;
_storeType = storeType?.Split('.')[1];
Expand Down Expand Up @@ -221,7 +224,8 @@ public async Task<List<string>> GetVaults(string storePath)
keys = res.Data.Subkeys;
}
// does it have an entry with the suffix we are looking for?
if (keys.Any(k => k.Key.Contains(suffix)))
var key = keys.FirstOrDefault(k => k.Key.EndsWith(suffix));
if (key.Key != null)
{
if (_storeType == StoreType.HCVKVPEM)
{
Expand All @@ -230,7 +234,7 @@ public async Task<List<string>> GetVaults(string storePath)
}
else
{
vaultPaths.Add(storePath + path);
vaultPaths.Add(storePath + path + "/" + key.Key);
}
}
}
Expand All @@ -255,16 +259,22 @@ public async Task<List<string>> GetVaults(string storePath)
public async Task PutCertificate(string certName, string contents, string pfxPassword, bool includeChain)
{
logger.MethodEntry();
try
{
if (_storeType != StoreType.HCVKVPEM)
{
await PutCertificateIntoFileStore(certName, contents, pfxPassword, includeChain);
return;
}
// for PEM stores, the store path is the container name, not entry name as with file stores

if (_storeType != StoreType.HCVKVPEM)
await PutCertificateIntoPemStore(certName, contents, pfxPassword, includeChain);
}
catch (Exception ex)
{
await PutCertificateIntoFileStore(certName, contents, pfxPassword, includeChain);
return;
logger.LogError(ex, "Error when adding the new certificate.");
throw;
}
// for PEM stores, the store path is the container name, not entry name as with file stores

await PutCertificateIntoPemStore(certName, contents, pfxPassword, includeChain);

logger.MethodExit();
}
public async Task PutCertificateIntoPemStore(string certName, string contents, string pfxPassword, bool includeChain)
Expand Down Expand Up @@ -407,7 +417,7 @@ private async Task PutCertificateIntoFileStore(string certName, string contents,
try
{
// first get entry contents and passphrase

logger.LogTrace("getting all secrets in the parent container for the store.");

if (string.IsNullOrEmpty(_mountPoint))
{
Expand All @@ -418,43 +428,56 @@ private async Task PutCertificateIntoFileStore(string certName, string contents,
res = await VaultClient.V1.Secrets.KeyValue.V2.ReadSecretAsync(parentPath, mountPoint: _mountPoint);
}
certData = (Dictionary<string, object>)res.Data.Data;
logger.LogTrace("got secret data.", certData);

string certificate = null;
string passphrase = null;

//Validates if the "certificate" and "private_key" keys exist in certData

var key = _storePath.Substring(_storePath.LastIndexOf("/"));
key = key.TrimStart('/');

logger.LogTrace($"getting the contents of {key}");

if (!certData.TryGetValue(key, out object certFileObj))
{
throw new DirectoryNotFoundException($"entry named {key} not found at {parentPath}");
}
certificate = certFileObj.ToString();

if (!certData.TryGetValue("passphrase", out object passphraseObj))
{
throw new DirectoryNotFoundException($"no passphrase entry found at {parentPath}");
}
passphrase = passphraseObj.ToString();

logger.LogTrace("got passphrase and certificate store secrets from vault.");

logger.LogTrace("calling method to add certificate to store file.");
// get new store entry
var newEntry = fileStore.AddCertificate(certName, pfxPassword, contents, includeChain, certificate, passphrase);

logger.LogTrace("got new store file.");
// write new store entry

try
{
logger.LogTrace("writing file store with new certificate to vault.");
VaultClient.V1.Auth.ResetVaultToken();
VaultClient.V1.Auth.ResetVaultToken();

var newData = new Dictionary<string, object> { { key, newEntry } };
var patchReq = new PatchSecretDataRequest() { Data = newData };

// temporary debugging code
var stringContent = new StringContent(JsonSerializer.Serialize(newData), Encoding.UTF8);
//

if (_mountPoint == null)
if (string.IsNullOrEmpty(_mountPoint))
{
await VaultClient.V1.Secrets.KeyValue.V2.WriteSecretAsync(_storePath, newEntry);
await VaultClient.V1.Secrets.KeyValue.V2.PatchSecretAsync(parentPath, patchReq);
}
else
{
await VaultClient.V1.Secrets.KeyValue.V2.WriteSecretAsync(_storePath, newEntry, mountPoint: _mountPoint);
await VaultClient.V1.Secrets.KeyValue.V2.PatchSecretAsync(parentPath, patchReq, _mountPoint);
}
}
catch (Exception ex)
Expand Down Expand Up @@ -570,7 +593,7 @@ public async Task<IEnumerable<CurrentInventoryItem>> GetCertificatesFromFileStor

//file stores for JKS, PKCS12 and PFX will have a "passphrase" entry on the same level by convention. We'll need this in order to extract the certificates for inventory.
var pos = _storePath.LastIndexOf("/");
var parentPath = _storePath.Substring(pos);
var parentPath = _storePath.Substring(0, pos);

try
{
Expand Down Expand Up @@ -607,7 +630,7 @@ public async Task<IEnumerable<CurrentInventoryItem>> GetCertificatesFromFileStor
break;

default:
throw new InvalidOperationException($"unrecognized store type vaule {_storeType}");
throw new InvalidOperationException($"unrecognized store type value {_storeType}");
}

try
Expand All @@ -624,7 +647,7 @@ public async Task<IEnumerable<CurrentInventoryItem>> GetCertificatesFromFileStor
//private List<CurrentInventoryItem> GetCertsFromJKS(Dictionary<string, object> certFields)
//{
// logger.MethodEntry();
// // certFields should contain two entries. The certificate with the "jks-contents" suffix, and "password"
// // certFields should contain two entries. The certificate with the "_jks" suffix, and "passphrase"

// string password;
// string base64encodedCert;
Expand Down
2 changes: 1 addition & 1 deletion hashicorp-vault-orchestrator/Jobs/Discovery.cs
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ public JobResult ProcessJob(DiscoveryJobConfiguration config, SubmitDiscoveryUpd
{
logger.LogError("Attempt to perform discovery on unsupported Secrets Engine backend.");

result.FailureMessage = $"{StoreType} does not support Discovery jobs.";
result.FailureMessage = $"{_storeType} does not support Discovery jobs.";
}
else
{
Expand Down
Loading

0 comments on commit b9707fd

Please sign in to comment.