KFDtool/sw/control/KFDtool.Container/ContainerUtilities.cs

351 lines
14 KiB
C#
Raw Normal View History

2020-08-01 08:12:20 -06:00
using System;
using System.Collections.ObjectModel;
using System.IO;
using System.Security.Cryptography;
using System.Security.Cryptography.Xml;
using System.Text;
using System.Xml;
using System.Xml.Serialization;
using System.Xml.XPath;
namespace KFDtool.Container
{
public class ContainerUtilities
{
public static byte[] GenerateSalt(int saltLength)
{
byte[] salt = new byte[saltLength];
using (RNGCryptoServiceProvider rngCsp = new RNGCryptoServiceProvider())
{
rngCsp.GetBytes(salt);
}
return salt;
}
public static byte[] Pbkdf2DeriveKeyFromPassword(string password, byte[] salt, int iterationCount, string hashAlgorithm, int keyLength)
{
Rfc2898DeriveBytes pbkdf2;
if (hashAlgorithm == "SHA512")
{
pbkdf2 = new Rfc2898DeriveBytes(Encoding.UTF8.GetBytes(password), salt, iterationCount, HashAlgorithmName.SHA512);
}
else
{
throw new Exception(string.Format("invalid hash algorithm: {0}", hashAlgorithm));
}
return pbkdf2.GetBytes(keyLength);
}
public static InnerContainer CreateInnerContainer()
{
InnerContainer innerContainer = new InnerContainer();
innerContainer.Version = "1.0";
innerContainer.Keys = new ObservableCollection<KeyItem>();
innerContainer.NextKeyNumber = 1;
innerContainer.Groups = new ObservableCollection<GroupItem>();
innerContainer.NextGroupNumber = 1;
return innerContainer;
}
public static XmlDocument SerializeInnerContainer(InnerContainer innerContainer)
{
XmlDocument doc = new XmlDocument();
XmlDeclaration xmlDeclaration = doc.CreateXmlDeclaration("1.0", "UTF-8", null);
doc.InsertBefore(xmlDeclaration, doc.DocumentElement);
XPathNavigator nav = doc.CreateNavigator();
using (XmlWriter w = nav.AppendChild())
{
XmlSerializerNamespaces ns = new XmlSerializerNamespaces();
ns.Add(string.Empty, string.Empty); // remove xsd and xsi attributes
XmlSerializer s = new XmlSerializer(typeof(InnerContainer));
s.Serialize(w, innerContainer, ns);
}
return doc;
}
public static (OuterContainer Container, byte[] Key) CreateOuterContainer(string password)
{
string outerVersion = "1.0";
int saltLength = 32; // critical security parameter
int iterationCount = 100000; // critical security parameter
string hashAlgorithm = "SHA512"; // critical security parameter
int keyLength = 32; // critical security parameter
byte[] salt = GenerateSalt(saltLength);
byte[] key = Pbkdf2DeriveKeyFromPassword(password, salt, iterationCount, hashAlgorithm, keyLength);
OuterContainer outerContainer = new OuterContainer();
outerContainer.Version = outerVersion;
outerContainer.KeyDerivation = new KeyDerivation();
outerContainer.KeyDerivation.DerivationAlgorithm = "PBKDF2";
outerContainer.KeyDerivation.HashAlgorithm = hashAlgorithm;
outerContainer.KeyDerivation.Salt = salt;
outerContainer.KeyDerivation.IterationCount = iterationCount;
outerContainer.KeyDerivation.KeyLength = keyLength;
outerContainer.EncryptedDataPlaceholder = "placeholder";
return (outerContainer, key);
}
public static XmlDocument SerializeOuterContainer(OuterContainer outerContainer)
{
XmlDocument doc = new XmlDocument();
XmlDeclaration xmlDeclaration = doc.CreateXmlDeclaration("1.0", "UTF-8", null);
doc.InsertBefore(xmlDeclaration, doc.DocumentElement);
XPathNavigator nav = doc.CreateNavigator();
using (XmlWriter w = nav.AppendChild())
{
XmlSerializerNamespaces ns = new XmlSerializerNamespaces();
ns.Add(string.Empty, string.Empty); // remove xsd and xsi attributes
XmlSerializer s = new XmlSerializer(typeof(OuterContainer));
s.Serialize(w, outerContainer, ns);
}
return doc;
}
public static byte[] EncryptOuterContainer(OuterContainer outerContainer, InnerContainer innerContainer, byte[] key)
{
XmlDocument outerContainerXml = SerializeOuterContainer(outerContainer);
XmlDocument innerContainerXml = SerializeInnerContainer(innerContainer);
XmlElement encryptedDataPlaceholder = outerContainerXml.GetElementsByTagName("EncryptedDataPlaceholder")[0] as XmlElement;
XmlElement plaintextInnerContainer = innerContainerXml.GetElementsByTagName("InnerContainer")[0] as XmlElement;
EncryptedData encryptedData = new EncryptedData();
encryptedData.Type = EncryptedXml.XmlEncElementUrl;
encryptedData.EncryptionMethod = new EncryptionMethod(EncryptedXml.XmlEncAES256Url);
EncryptedXml encryptedXml = new EncryptedXml();
using (AesCryptoServiceProvider aesCsp = new AesCryptoServiceProvider())
{
aesCsp.KeySize = 256; // critical security parameter
aesCsp.Key = key; // critical security parameter
aesCsp.Mode = CipherMode.CBC; // critical security parameter
aesCsp.GenerateIV(); // critical security parameter
encryptedData.CipherData.CipherValue = encryptedXml.EncryptData(plaintextInnerContainer, aesCsp, false);
}
EncryptedXml.ReplaceElement(encryptedDataPlaceholder, encryptedData, false);
byte[] outerContainerBytes = Encoding.UTF8.GetBytes(outerContainerXml.OuterXml);
byte[] fileBytes = Shared.Utility.Compress(outerContainerBytes);
return fileBytes;
}
public static (OuterContainer ContainerOuter, InnerContainer ContainerInner, byte[] Key) DecryptOuterContainer(byte[] fileBytes, string password)
{
byte[] outerContainerBytes;
try
{
outerContainerBytes = Shared.Utility.Decompress(fileBytes);
}
catch (Exception ex)
{
throw new Exception(string.Format("failed to decompress (corrupted or not a valid container file): {0}", ex.Message));
}
XmlDocument outerContainerXml = new XmlDocument();
outerContainerXml.LoadXml(Encoding.UTF8.GetString(outerContainerBytes));
string version;
try
{
version = outerContainerXml.SelectSingleNode("/OuterContainer/@version").Value;
}
catch (Exception ex)
{
throw new Exception(string.Format("unable to read /OuterContainer/@version: {0}", ex.Message));
}
Version parsedVersion = new Version(version);
if (parsedVersion.Major != 1)
{
throw new Exception(string.Format("outer container version too new (this container was written on a newer software version) - expected 1, got {0}", parsedVersion.Major));
}
OuterContainer outerContainer = new OuterContainer();
string outerVersion = "1.0";
outerContainer.Version = outerVersion;
outerContainer.KeyDerivation = new KeyDerivation();
byte[] key;
string derivationAlgorithm;
try
{
derivationAlgorithm = outerContainerXml.SelectSingleNode("/OuterContainer/KeyDerivation/DerivationAlgorithm").InnerText;
}
catch (Exception ex)
{
throw new Exception(string.Format("invalid KeyDerivation DerivationAlgorithm: {0}", ex.Message));
}
outerContainer.KeyDerivation.DerivationAlgorithm = derivationAlgorithm;
if (derivationAlgorithm == "PBKDF2")
{
string hashAlgorithm;
try
{
hashAlgorithm = outerContainerXml.SelectSingleNode("/OuterContainer/KeyDerivation/HashAlgorithm").InnerText;
}
catch (Exception ex)
{
throw new Exception(string.Format("invalid KeyDerivation HashAlgorithm: {0}", ex.Message));
}
outerContainer.KeyDerivation.HashAlgorithm = hashAlgorithm;
byte[] salt;
try
{
salt = Convert.FromBase64String(outerContainerXml.SelectSingleNode("/OuterContainer/KeyDerivation/Salt").InnerText);
}
catch (Exception ex)
{
throw new Exception(string.Format("invalid KeyDerivation Salt: {0}", ex.Message));
}
outerContainer.KeyDerivation.Salt = salt;
int iterationCount;
try
{
iterationCount = Convert.ToInt32(outerContainerXml.SelectSingleNode("/OuterContainer/KeyDerivation/IterationCount").InnerText);
}
catch (Exception ex)
{
throw new Exception(string.Format("invalid KeyDerivation IterationCount: {0}", ex.Message));
}
outerContainer.KeyDerivation.IterationCount = iterationCount;
int keyLength;
try
{
keyLength = Convert.ToInt32(outerContainerXml.SelectSingleNode("/OuterContainer/KeyDerivation/KeyLength").InnerText);
}
catch (Exception ex)
{
throw new Exception(string.Format("invalid KeyDerivation KeyLength: {0}", ex.Message));
}
outerContainer.KeyDerivation.KeyLength = keyLength;
key = Pbkdf2DeriveKeyFromPassword(password, salt, iterationCount, hashAlgorithm, keyLength);
}
else
{
throw new Exception(string.Format("unsupported KeyDerivation DerivationAlgorithm: {0}", derivationAlgorithm));
}
outerContainer.EncryptedDataPlaceholder = "placeholder";
XmlElement encryptedDataElement = outerContainerXml.GetElementsByTagName("EncryptedData")[0] as XmlElement;
if (encryptedDataElement == null)
{
throw new Exception("unable to find EncryptedData");
}
EncryptedData encryptedData = new EncryptedData();
encryptedData.LoadXml(encryptedDataElement);
EncryptedXml encryptedXml = new EncryptedXml();
byte[] innerContainerBytes;
using (AesCryptoServiceProvider aesCsp = new AesCryptoServiceProvider())
{
aesCsp.KeySize = 256; // critical security parameter
aesCsp.Key = key; // critical security parameter
aesCsp.Mode = CipherMode.CBC; // critical security parameter
aesCsp.GenerateIV(); // critical security parameter
try
{
innerContainerBytes = encryptedXml.DecryptData(encryptedData, aesCsp);
}
catch (Exception ex)
{
throw new Exception(string.Format("unable to decrypt InnerContainer (check password): {0}", ex.Message));
}
}
XmlDocument innerContainerXml = new XmlDocument();
try
{
innerContainerXml.LoadXml(Encoding.UTF8.GetString(innerContainerBytes));
}
catch (Exception ex)
{
throw new Exception(string.Format("unable to parse InnerContainer (check password): {0}", ex.Message));
}
string innerReadVersion;
try
{
innerReadVersion = innerContainerXml.SelectSingleNode("/InnerContainer/@version").Value;
}
catch (Exception ex)
{
throw new Exception(string.Format("unable to read /InnerContainer/@version: {0}", ex.Message));
}
Version innerReadVersionParsed = new Version(innerReadVersion);
if (innerReadVersionParsed.Major != 1)
{
throw new Exception(string.Format("inner container version too new (this container was written on a newer software version) - expected 1, got {0}", innerReadVersionParsed.Major));
}
InnerContainer innerContainer;
using (XmlReader xmlReader = new XmlNodeReader(innerContainerXml))
{
try
{
innerContainer = (InnerContainer)new XmlSerializer(typeof(InnerContainer)).Deserialize(xmlReader);
}
catch (Exception ex)
{
throw new Exception(string.Format("unable to deserialize InnerContainer: {0}", ex.Message));
}
}
return (outerContainer, innerContainer, key);
}
}
}