using Leosac.KeyManager.Library.DivInput;
using Newtonsoft.Json;
using System.Text;
namespace Leosac.KeyManager.Library.KeyStore
{
///
/// The base class for a Key Store implementation.
///
public abstract class KeyStore
{
private static readonly log4net.ILog log = log4net.LogManager.GetLogger(System.Reflection.MethodBase.GetCurrentMethod()?.DeclaringType);
public const string ATTRIBUTE_NAME = "name";
public const string ATTRIBUTE_HEXNAME = "hexname";
public const string ATTRIBUTE_PUBVAR = "pubvar";
public const string ATTRIBUTE_HEXPUBVAR = "hexpubvar";
protected readonly JsonSerializerSettings _jsonSettings;
protected KeyStore()
{
_jsonSettings = KeyEntry.CreateJsonSerializerSettings();
DefaultKeyEntries = new Dictionary();
Attributes = new Dictionary();
}
///
/// The key store name.
///
public abstract string Name { get; }
///
/// True to allow key entry creation, false otherwise.
///
public abstract bool CanCreateKeyEntries { get; }
///
/// True to allow key entry deletion, false otherwise.
///
public abstract bool CanDeleteKeyEntries { get; }
///
/// True to allow key entry update, false otherwise.
///
public virtual bool CanUpdateKeyEntries => true;
///
/// True if key entries can be reordered, false otherwise.
///
public virtual bool CanReorderKeyEntries => false;
///
/// True if key entries can have a custom label, false otherwise.
///
public virtual bool CanDefineKeyEntryLabel => true;
///
/// Get the supported key entry classes.
///
public abstract IEnumerable SupportedClasses { get; }
///
/// The key store properties.
///
public KeyStoreProperties? Properties { get; set; }
public IDictionary DefaultKeyEntries { get; set; }
public IDictionary Attributes { get; }
public StoreOptions? Options { get; set; }
public Task CheckKeyEntryExists(KeyEntry keyEntry)
{
return CheckKeyEntryExists(keyEntry.Identifier, keyEntry.KClass);
}
///
/// Open the key store.
///
public abstract Task Open();
///
/// Close the key store.
///
public abstract Task Close();
///
/// Check if a key entry exists.
///
/// The key entry identifier
/// The key entry class
///
public abstract Task CheckKeyEntryExists(KeyEntryId identifier, KeyEntryClass keClass);
///
/// Get all key entry identifiers.
///
/// List of key entry identifiers
public Task> GetAll()
{
return GetAll(null);
}
///
/// Get all key entry identifiers.
///
/// The key entry class (optional, null means all)
/// List of key entry identifiers
public abstract Task> GetAll(KeyEntryClass? keClass);
///
/// Create a new key entry.
///
/// The key entry details
public abstract Task Create(IChangeKeyEntry keyEntry);
///
/// Generate a new key entry.
///
/// The new key entry identifier
/// The key entry class
/// The key entry identifier
public virtual Task Generate(KeyEntryId? identifier, KeyEntryClass keClass)
{
log.Error("The key store doesn't support key entry generation without specifing the target type.");
throw new NotImplementedException();
}
///
/// Generate a new key entry.
///
/// The new key entry
/// The key entry identifier
public virtual async Task Generate(KeyEntry keyEntry)
{
if (keyEntry.Variant != null && keyEntry.KClass == KeyEntryClass.Symmetric)
{
foreach (var kv in keyEntry.Variant.KeyContainers)
{
if (kv.Key.KeySize > 0)
{
foreach (var m in kv.Key.Materials)
{
m.SetValueAsBinary(KeyGeneration.Random(kv.Key.KeySize));
}
}
}
}
else
{
log.Error(string.Format("The key store doesn't support key entry generation for class `{0}`.", keyEntry.KClass));
throw new NotImplementedException();
}
await Create(keyEntry);
return keyEntry.Identifier;
}
///
/// Get a key entry.
///
/// The key entry identifier
/// The key entry class
/// The key entry
public abstract Task Get(KeyEntryId identifier, KeyEntryClass keClass);
///
/// Update an existing key entry.
///
/// The key entry details
/// Ignore if the targeted key entry is missing, throw otherwise.
public abstract Task Update(IChangeKeyEntry keyEntry, bool ignoreIfMissing);
///
/// Update an existing key entry.
///
/// The key entry details
public Task Update(IChangeKeyEntry keyEntry)
{
return Update(keyEntry, false);
}
///
/// Delete an existing key entry.
///
/// The key entry identifier
/// The key entry class
/// Ignore if the targeted key entry is missing, throw otherwise.
public abstract Task Delete(KeyEntryId identifier, KeyEntryClass keClass, bool ignoreIfMissing);
///
/// Delete an existing key entry.
///
/// The key entry identifier
/// The key entry class
public Task Delete(KeyEntryId identifier, KeyEntryClass keClass)
{
return Delete(identifier, keClass, false);
}
///
/// Move up a key entry on the list, if reordering is supported.
///
/// The key entry identifier
/// The key entry class
public virtual Task MoveUp(KeyEntryId identifier, KeyEntryClass keClass)
{
log.Info(string.Format("Moving Up key entry `{0}` of class `{1}`...", identifier, keClass));
log.Error("The key store doesn't support key entries reordering.");
throw new KeyStoreException("The key store doesn't support key entries reordering.");
}
///
/// Move down a key entry on the list, if reordering is supported.
///
/// The key entry identifier
/// The key entry class
public virtual Task MoveDown(KeyEntryId identifier, KeyEntryClass keClass)
{
log.Info(string.Format("Moving Down key entry `{0}` of class `{1}`...", identifier, keClass));
log.Error("The key store doesn't support key entries reordering.");
throw new KeyStoreException("The key store doesn't support key entries reordering.");
}
///
/// Store a key entry change.
///
/// The key entry details.
public virtual Task Store(IChangeKeyEntry change)
{
return Store(new List
{
change
});
}
///
/// Store a list of key entry changes.
///
/// The key entries details.
public abstract Task Store(IList changes);
public virtual async Task Publish(KeyStore store, Func getFavoriteKeyStore, Action? initCallback)
{
var classes = SupportedClasses;
foreach (var keClass in classes)
{
await Publish(store, getFavoriteKeyStore, keClass, initCallback);
}
}
public virtual async Task Publish(KeyStore store, Func getFavoriteKeyStore, KeyEntryClass keClass, Action? initCallback)
{
await Publish(store, getFavoriteKeyStore, keClass, null, initCallback);
}
public virtual async Task Publish(KeyStore store, Func getFavoriteKeyStore, KeyEntryClass keClass, IEnumerable? ids, Action? initCallback)
{
var changes = new List();
if (ids == null)
{
ids = await GetAll(keClass);
}
initCallback?.Invoke(this, keClass, ids.Count());
if (!string.IsNullOrEmpty(Options?.PublishVariable))
{
Attributes[ATTRIBUTE_PUBVAR] = Options.PublishVariable;
Attributes[ATTRIBUTE_HEXPUBVAR] = Convert.ToHexString(Encoding.UTF8.GetBytes(Options.PublishVariable));
}
foreach (var id in ids)
{
var entry = await Get(id, keClass);
if (entry != null)
{
var resolveKeyLinks = (Options?.ResolveKeyLinks).GetValueOrDefault(true);
var resolveVariables = (Options?.ResolveVariables).GetValueOrDefault(true);
entry.Identifier = entry.Identifier.Clone(resolveVariables ? Attributes : null);
if (entry.Link != null && entry.Link.KeyIdentifier.IsConfigured() && !string.IsNullOrEmpty(entry.Link.KeyStoreFavorite))
{
if (resolveKeyLinks)
{
var cryptogram = new KeyEntryCryptogram
{
Identifier = entry.Identifier
// TODO: we may want to have a different wrapping key per Cryptogram later on
};
var ks = getFavoriteKeyStore(entry.Link.KeyStoreFavorite);
if (ks != null)
{
await ks.Open();
try
{
var divContext = new DivInput.DivInputContext
{
KeyStore = ks,
KeyEntry = entry
};
cryptogram.Value = await ks.ResolveKeyEntryLink(entry.Link.KeyIdentifier.Clone(resolveVariables ? Attributes : null), keClass, ComputeDivInput(divContext, entry.Link.DivInput), entry.Link.WrappingKey);
}
finally
{
await ks.Close();
}
}
changes.Add(cryptogram);
}
else
{
if (resolveVariables)
{
entry.Link.KeyIdentifier = entry.Link.KeyIdentifier.Clone(Attributes);
}
changes.Add(entry);
}
}
else
{
if (entry.Variant != null)
{
foreach (var kv in entry.Variant.KeyContainers)
{
if (kv.Key.Link != null && kv.Key.Link.KeyIdentifier.IsConfigured() && !string.IsNullOrEmpty(kv.Key.Link.KeyStoreFavorite))
{
if (resolveKeyLinks)
{
var ks = getFavoriteKeyStore(kv.Key.Link.KeyStoreFavorite);
if (ks != null)
{
await ks.Open();
try
{
var divContext = new DivInput.DivInputContext
{
KeyStore = ks,
KeyEntry = entry,
KeyContainer = kv
};
kv.Key.SetAggregatedValueAsString(await ks.ResolveKeyLink(kv.Key.Link.KeyIdentifier.Clone(resolveVariables ? Attributes : null), keClass, kv.Key.Link.ContainerSelector, ComputeDivInput(divContext, kv.Key.Link.DivInput)));
}
finally
{
await ks.Close();
}
}
// We remove link information from the being pushed key entry
kv.Key.Link = new KeyLink();
}
else
{
if (resolveVariables)
{
kv.Key.Link.KeyIdentifier = kv.Key.Link.KeyIdentifier.Clone(Attributes);
}
}
}
}
}
changes.Add(entry);
}
}
}
await store.Open();
try
{
if (!(Options?.DryRun).GetValueOrDefault(false))
{
await store.Store(changes);
}
else
{
log.Info("Dry Run, skipping the storage of key entries.");
}
}
finally
{
await store.Close();
}
}
public virtual Task Diff(KeyStore store, Func getFavoriteKeyStore, KeyEntryClass keClass, IEnumerable? ids, Action? initCallback)
{
throw new NotImplementedException();
}
private static string? ComputeDivInput(DivInputContext divContext, IList divInput)
{
divContext.CurrentDivInput = null;
if (divInput != null && divInput.Count > 0)
{
divContext.CurrentDivInput = string.Empty;
foreach (var input in divInput)
{
divContext.CurrentDivInput += input.GetFragment(divContext);
}
}
return divContext.CurrentDivInput;
}
protected void OnKeyEntryRetrieved(KeyEntry keyEntry)
{
KeyEntryRetrieved?.Invoke(this, keyEntry);
}
protected void OnKeyEntryUpdated(IChangeKeyEntry keyEntry)
{
KeyEntryUpdated?.Invoke(this, keyEntry);
}
///
/// Get a key from a key entry.
///
/// The key entry identifier
/// The key entry class
public Task GetKey(KeyEntryId keyIdentifier, KeyEntryClass keClass)
{
return GetKey(keyIdentifier, keClass, null);
}
///
/// Get a key from a key entry.
///
/// The key entry identifier
/// The key entry class
/// The key container selector
///
public async Task GetKey(KeyEntryId keyIdentifier, KeyEntryClass keClass, string? keyContainerSelector)
{
log.Info(String.Format("Getting key with Key Entry Identifier `{0}` and Container Selector `{1}`...", keyIdentifier, keyContainerSelector));
var keyEntry = await Get(keyIdentifier, keClass);
if (keyEntry != null)
{
if (keyEntry.Variant != null)
{
KeyContainer? kv;
if (!string.IsNullOrEmpty(keyContainerSelector))
{
kv = keyEntry.Variant.KeyContainers.OfType().Where(kv => kv.Version.ToString() == keyContainerSelector).FirstOrDefault();
}
else
{
kv = keyEntry.Variant.KeyContainers[0];
}
if (kv != null)
{
return kv.Key;
}
else
{
log.Error(String.Format("Cannot found the key container with selector `{0}` on key entry `{1}`", keyContainerSelector, keyIdentifier));
}
}
else
{
log.Error("No key variant set on the Key Entry.");
}
}
else
{
log.Error(String.Format("Key Entry Identifier `{0}` cannot be found.", keyIdentifier));
}
return null;
}
public async Task GetKeyValue(KeyEntryId keyIdentifier, KeyEntryClass keClass, string? keyContainerSelector, string? divInput)
{
if (!string.IsNullOrEmpty(divInput))
{
log.Error("Div Input parameter is not yet supported.");
throw new KeyStoreException("Div Input parameter is not yet supported.");
}
var key = await GetKey(keyIdentifier, keClass, keyContainerSelector);
return key?.GetAggregatedValueAsString();
}
///
/// Resolve a key entry link.
///
/// The key entry identifier
/// The key entry class
/// The key div input (optional)
/// The wrapping key for cryptogram computation (optional)
/// The change key entry cryptogram
public virtual async Task ResolveKeyEntryLink(KeyEntryId keyIdentifier, KeyEntryClass keClass, string? divInput, WrappingKey? wrappingKey)
{
string? result = null;
log.Info(string.Format("Resolving key entry link with Key Entry Identifier `{0}`, Div Input `{1}`...", keyIdentifier, divInput));
var keyEntry = await Get(keyIdentifier, keClass);
if (keyEntry != null)
{
log.Info("Key entry link resolved.");
if (wrappingKey != null && wrappingKey.KeyId.IsConfigured())
{
var wKey = await GetKey(wrappingKey.KeyId, KeyEntryClass.Symmetric, wrappingKey.ContainerSelector);
if (wKey != null)
{
// TODO: do something here to encipher the key value?
// The wrapping algorithm may be too close to the targeted Key Store
throw new NotSupportedException();
}
else
{
log.Error("Cannot resolve the wrapping key.");
}
}
else
{
result = JsonConvert.SerializeObject(keyEntry, typeof(KeyEntry), _jsonSettings);
}
}
else
{
log.Error("Cannot resolve the key entry link.");
}
log.Info("Key entry link completed.");
return result;
}
///
/// Resolve a key link.
///
/// The key entry identifier
/// The key entry class
/// The key container selector (optional)
/// The key div input (optional)
/// The key value
public virtual async Task ResolveKeyLink(KeyEntryId keyIdentifier, KeyEntryClass keClass, string? containerSelector, string? divInput)
{
log.Info(string.Format("Resolving key link with Key Entry Identifier `{0}`, Container Selector `{1}`, Div Input `{2}`...", keyIdentifier, containerSelector, divInput));
if (!await CheckKeyEntryExists(keyIdentifier, keClass))
{
log.Error(string.Format("The key entry `{0}` do not exists.", keyIdentifier));
throw new KeyStoreException("The key entry do not exists.");
}
var result = await GetKeyValue(keyIdentifier, keClass, containerSelector, divInput);
if (string.IsNullOrEmpty(result))
{
log.Warn("Key link returned an empty value.");
}
log.Info("Key link completed.");
return result;
}
public virtual KeyEntry? GetDefaultKeyEntry(KeyEntryClass keClass)
{
return GetDefaultKeyEntry(keClass, true);
}
public KeyEntry? GetDefaultKeyEntry(KeyEntryClass keClass, bool clone)
{
if (DefaultKeyEntries.ContainsKey(keClass))
{
return clone ? DefaultKeyEntries[keClass]?.DeepCopy() : DefaultKeyEntries[keClass];
}
return null;
}
public event EventHandler? KeyEntryRetrieved;
public event EventHandler? KeyEntryUpdated;
}
}