private XmlDocument CreateDocumentFromSecrets(IEnumerable <Secret> secrets) { XmlDocument document = CreateNewDocument(); if (secrets == null) { return(document); } // Check if there are any elements in the collection, and also get hold of the first element in the collection IEnumerator <Secret> enumerator = secrets.GetEnumerator(); if (!enumerator.MoveNext()) { if (New <ILogging>().IsWarningEnabled) { New <ILogging>().LogWarning($"{nameof(CreateDocumentFromSecrets)} No elements in collection"); } return(document); } // This is where we sort the secret collection into a session per key.... Right now we assume the same // key for all.... InternalEncryptionKey key = new InternalEncryptionKey(enumerator.Current.EncryptionKey); // Get some salt for the key-derivation function byte[] salt = New <IRandomGenerator>().Generate(16); SymmetricAlgorithm masterKey = DeriveMasterKey(key.DecryptPassphrase(), salt, _defaultRfc2898iterations); LastUpdateUtc = New <INow>().Utc; // Start building a session node, recording the used salt and the number of iterations XmlElement xecretsSession = document.CreateElement("XecretsSession"); xecretsSession.Attributes.Append(document.CreateAttribute("KeyDerivation")).Value = "Rfc2898"; xecretsSession.Attributes.Append(document.CreateAttribute("Salt")).Value = System.Convert.ToBase64String(salt); xecretsSession.Attributes.Append(document.CreateAttribute("Iterations")).Value = _defaultRfc2898iterations.ToString(CultureInfo.InvariantCulture); xecretsSession.Attributes.Append(document.CreateAttribute("LastUpdateUtc")).Value = LastUpdateUtc.ToString(CultureInfo.InvariantCulture); // Get the secrets collection in the form of an appropriate XML Element, suitable for encryption. XmlElement secretsElement = CreateSecretsElement(document, secrets); xecretsSession.AppendChild(secretsElement); // Encrypt the node, creating a named session key and embedding it in the output. EncryptedXml encryptedXml = new EncryptedXml(); encryptedXml.AddKeyNameMapping(KEYNAME, masterKey); EncryptedData encryptedData = encryptedXml.Encrypt(secretsElement, KEYNAME); // Replace the encrypted element with the encrypted same EncryptedXml.ReplaceElement(secretsElement, encryptedData, false); // Finally, actually append this to the set of sessions. document.SelectSingleNode("AxantumXecrets/XecretsSessions").AppendChild(xecretsSession); return(document); }
private static XmlDocument DecryptEncryptedData(InternalEncryptionKey internalKey, XmlElement sessionElement) { XmlElement secretsElement = sessionElement.FirstChild as XmlElement; if (secretsElement == null || secretsElement.Name != "EncryptedData") { throw new FormatException($"There must be an element named {nameof(EncryptedData)} here."); } SymmetricAlgorithm masterKey = GetAndDeriveMasterKey(internalKey.DecryptPassphrase(), sessionElement); // Decrypt the node, creating a named session key to use the embedded session key. EncryptedXml encryptedXml = new EncryptedXml(); encryptedXml.AddKeyNameMapping(KEYNAME, masterKey); EncryptedData encryptedData = new EncryptedData(); encryptedData.LoadXml(secretsElement); SymmetricAlgorithm decryptionKey; try { decryptionKey = encryptedXml.GetDecryptionKey(encryptedData, null); } catch (CryptographicException) { // This ok - we're not guaranteed that we actually provided the correct key. return(null); } // It seems mono doesn't throw, but returns a null key instead. if (decryptionKey == null) { return(null); } byte[] plainTextXml = encryptedXml.DecryptData(encryptedData, decryptionKey); string plain = encryptedXml.Encoding.GetString(plainTextXml); XmlDocument decryptedSecretsDocument = new XmlDocument(); decryptedSecretsDocument.LoadXml(plain); return(decryptedSecretsDocument); }