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; } }