using System; using System.Collections.Generic; using System.IO; using System.Security.Cryptography; using System.Security.Cryptography.Xml; using System.Text; using System.Xml; using System.Xml.Serialization; namespace KFDtool.Adapter.Bundle { public class Firmware { private const string PACKAGE_VERSION = "1.0"; private const string BODY_VERSION = "1.0"; private const string SIGNATURE_VERSION = "1.0"; private const string GENERATE_VERSION = "1.0"; private const string CANONICALIZATION_METHOD_URL = "http://www.w3.org/TR/2001/REC-xml-c14n-20010315"; private const string DIGEST_METHOD_URL = "http://www.w3.org/2001/04/xmlenc#sha512"; private static string GetDigest(XmlDocument doc) { XmlDsigC14NTransform t = new XmlDsigC14NTransform(); t.LoadInput(doc); Stream s = (Stream)t.GetOutput(typeof(Stream)); byte[] hash; using (SHA512 sha = new SHA512Managed()) { hash = sha.ComputeHash(s); } return Convert.ToBase64String(hash); } private static XmlDocument SerializeBody(List fw) { XmlDocument bodyDoc = new XmlDocument(); XmlElement body = bodyDoc.CreateElement(string.Empty, "Body", string.Empty); body.SetAttribute("version", BODY_VERSION); bodyDoc.AppendChild(body); foreach (Update fwi in fw) { XmlSerializer serializer = new XmlSerializer(typeof(Update)); string temp; using (StringWriter writer = new StringWriter()) { serializer.Serialize(writer, fwi); temp = writer.ToString(); } XmlDocument updDoc = new XmlDocument(); updDoc.LoadXml(temp); XmlNode fwu = bodyDoc.ImportNode(updDoc.SelectSingleNode("/Update"), true); body.AppendChild(fwu); } return bodyDoc; } private static List DeserializeBody(XmlDocument doc) { // check body version string bv = doc.SelectSingleNode("/Body/@version").Value; if (new Version(bv).Major != new Version(BODY_VERSION).Major) { throw new Exception(string.Format("unexpected body major version - expected: {0}, got: {1}", BODY_VERSION, bv)); } List updates = new List(); XmlSerializer serializer = new XmlSerializer(typeof(Update)); XmlNodeList nodeList = doc.SelectNodes("/Body/Update"); foreach (XmlNode no in nodeList) { Update upd; using (TextReader reader = new StringReader(no.OuterXml)) { upd = (Update)serializer.Deserialize(reader); } updates.Add(upd); } return updates; } private static List CreateUpdateList(string templatePath) { XmlDocument doc = new XmlDocument(); doc.LoadXml(File.ReadAllText(templatePath)); // check generate version string gv = doc.SelectSingleNode("/Generate/@version").Value; if (new Version(gv).Major != new Version(GENERATE_VERSION).Major) { throw new Exception(string.Format("unexpected generate major version - expected: {0}, got: {1}", GENERATE_VERSION, gv)); } XmlNodeList updateNodes = doc.SelectNodes("/Generate/Update"); List updateList = new List(); foreach (XmlNode updateNode in updateNodes) { Update update = new Update(); update.AppData = File.ReadAllBytes(updateNode.SelectSingleNode("AppDataPath").InnerText); update.AppVersion = updateNode.SelectSingleNode("AppVersion").InnerText; update.Audience = updateNode.SelectSingleNode("Audience").InnerText; update.RamBslData = File.ReadAllBytes(updateNode.SelectSingleNode("RamBslDataPath").InnerText); update.RamBslVersion = updateNode.SelectSingleNode("RamBslVersion").InnerText; XmlNodeList modelNodes = updateNode.SelectSingleNode("ProductModel").ChildNodes; List modelList = new List(); foreach (XmlNode modelNode in modelNodes) { Model model = new Model(); model.Name = modelNode.SelectSingleNode("Name").InnerText; XmlNodeList revisionNodes = modelNode.SelectSingleNode("Revision").ChildNodes; List revisionList = new List(); foreach (XmlNode revisionNode in revisionNodes) { revisionList.Add(revisionNode.InnerText); } model.Revision = revisionList; modelList.Add(model); } update.ProductModel = modelList; updateList.Add(update); } return updateList; } private static void CreateUpdatePackage(List fw, string outPath) { XmlDocument doc = new XmlDocument(); XmlDeclaration xmlDeclaration = doc.CreateXmlDeclaration("1.0", "UTF-8", null); doc.InsertBefore(xmlDeclaration, doc.DocumentElement); XmlElement fwupdate = doc.CreateElement(string.Empty, "FirmwarePackage", string.Empty); fwupdate.SetAttribute("version", PACKAGE_VERSION); doc.AppendChild(fwupdate); XmlDocument ext = SerializeBody(fw); XmlNode body = doc.ImportNode(ext.SelectSingleNode("/Body"), true); fwupdate.AppendChild(body); XmlDocument strippedDoc = new XmlDocument(); strippedDoc.LoadXml(body.OuterXml); XmlElement signature = doc.CreateElement(string.Empty, "Signature", string.Empty); signature.SetAttribute("version", SIGNATURE_VERSION); fwupdate.AppendChild(signature); XmlElement canonicalizationmethod = doc.CreateElement(string.Empty, "CanonicalizationMethod", string.Empty); canonicalizationmethod.SetAttribute("Algorithm", CANONICALIZATION_METHOD_URL); signature.AppendChild(canonicalizationmethod); XmlElement digestmethod = doc.CreateElement(string.Empty, "DigestMethod", string.Empty); digestmethod.SetAttribute("Algorithm", DIGEST_METHOD_URL); signature.AppendChild(digestmethod); XmlElement digestvalue = doc.CreateElement(string.Empty, "DigestValue", string.Empty); digestvalue.AppendChild(doc.CreateTextNode(GetDigest(strippedDoc))); signature.AppendChild(digestvalue); byte[] pkgData = Encoding.UTF8.GetBytes(doc.OuterXml); // write uncompressed firmware package file File.WriteAllBytes(outPath + ".ufp", pkgData); byte[] outData = Shared.Utility.Compress(pkgData); // write compressed firmware package file File.WriteAllBytes(outPath + ".cfp", outData); } public static List OpenCompressedUpdatePackage(string inPath) { byte[] inData = File.ReadAllBytes(inPath); byte[] pkgData = Shared.Utility.Decompress(inData); return OpenUpdatePackage(pkgData); } public static List OpenUncompressedUpdatePackage(string inPath) { byte[] pkgData = File.ReadAllBytes(inPath); return OpenUpdatePackage(pkgData); } private static List OpenUpdatePackage(byte[] pkgData) { string pkg = Encoding.UTF8.GetString(pkgData); XmlDocument doc = new XmlDocument(); doc.LoadXml(pkg); // check package version string pv = doc.SelectSingleNode("/FirmwarePackage/@version").Value; if (new Version(pv).Major != new Version(PACKAGE_VERSION).Major) { throw new Exception(string.Format("unexpected package major version - expected: {0}, got: {1}", PACKAGE_VERSION, pv)); } string sv = doc.SelectSingleNode("/FirmwarePackage/Signature/@version").Value; // check signature version if (new Version(sv).Major != new Version(SIGNATURE_VERSION).Major) { throw new Exception(string.Format("unexpected signature major version - expected: {0}, got: {1}", SIGNATURE_VERSION, sv)); } // check canonicalization method string cm = doc.SelectSingleNode("/FirmwarePackage/Signature/CanonicalizationMethod/@Algorithm").Value; if (!cm.Equals(CANONICALIZATION_METHOD_URL)) { throw new Exception(string.Format("unexpected canonicalization method - expected: {0}, got: {1}", CANONICALIZATION_METHOD_URL, cm)); } // check digest method string dm = doc.SelectSingleNode("/FirmwarePackage/Signature/DigestMethod/@Algorithm").Value; if (!dm.Equals(DIGEST_METHOD_URL)) { throw new Exception(string.Format("unexpected digest method - expected: {0}, got: {1}", DIGEST_METHOD_URL, dm)); } string expectedDigest = doc.SelectSingleNode("/FirmwarePackage/Signature/DigestValue").InnerText; XmlDocument strippedDoc = new XmlDocument(); strippedDoc.LoadXml(doc.SelectSingleNode("/FirmwarePackage/Body").OuterXml); string calculatedDigest = GetDigest(strippedDoc); // check package integrity if (!expectedDigest.Equals(calculatedDigest)) { throw new Exception(string.Format("digest value does not match - expected: {0}, calculated: {1}", expectedDigest, calculatedDigest)); } return DeserializeBody(strippedDoc); } public static void GenerateUpdate(string input, string output) { List updates = CreateUpdateList(input); CreateUpdatePackage(updates, output); } } }