/// <summary> /// What to do when the selection changes in the QR Code site list: /// </summary> /// <param name="sender"></param> /// <param name="e"></param> private void cmboExportSiteQR_SelectedIndexChanged(object sender, EventArgs e) { // Asbestos underpants: try { // Get the current selection of the combo box and make sure it // isn't empty: String site = (string)cmboExportSiteQR.Text; if (!String.IsNullOrEmpty(site)) { // Ask the main form to give us the site parameters for this site. // Since the main form has all the code to do this, we'll ask it // to do the dirty work. SiteParameters siteParams = caller.GetSiteParamsForQRCode(site); // Now we'll generate the text we'll embed into the QR Code. We want // this to be as compact as we can get it, so our "headings" will be // single letters. We'll start off with an identifying header so the // QR Code reader will know the format of our string. We delimite the // string with pipes, which aren't allowed in any of our fields. We // want this to match as closely to the values of the XML export file // for consistency. That means the hash engine and the character // limit fields will need some tweaking. StringBuilder sb = new StringBuilder(); sb.Append("CRYPTNOSv1|" + "S:" + siteParams.Site + "|" + "H:" + HashEngine.HashEnumStringToDisplayHash(siteParams.Hash) + "|" + "I:" + siteParams.Iterations.ToString() + "|" + "C:" + siteParams.CharTypes.ToString() + "|L:"); if (siteParams.CharLimit < 0) { sb.Append("0"); } else { sb.Append(siteParams.CharLimit.ToString()); } // Now that we've built our string, use the QRCodeWriter from ZXing to // build the QR Code image and assign the bitmap to the picture box: byteMatrix = qrCodeWriter.encode(sb.ToString(), BarcodeFormat.QR_CODE, 200, 200); pictureBox1.Image = byteMatrix.ToBitmap(); } // If the selection in the combo box wasn't useful, empty the picture box: else { pictureBox1.Image = null; } } // Similarly, if anything blew up, empty the picture box: catch { pictureBox1.Image = null; } }
/// <summary> /// Read a <see cref="List"/> of <see cref="SiteParameters"/> from the new XML-based /// cross-platform export file format /// </summary> /// <param name="filename">A string containing the full path to the import file</param> /// <param name="password">A string containing the password used to decrypt the /// file</param> /// <returns>A <see cref="List"/> of <see cref="SiteParameters"/></returns> /// <exception cref="ImportHandlerException">Thrown if a parsing error occurs during /// the import process</exception> /// <exception cref="Exception">Thrown if anything else blows up along the way</exception> private static List <SiteParameters> ImportFromXMLv1File(string filename, string password) { try { // Declare somewhere to hold the site count as read from the file: int siteCount = 0; // Try to open a file stream for the file: FileStream fs = new FileStream(filename, FileMode.Open); // The process below will blow up if we try to read a file that is so large // it exceeds the 32-bit integer max value. This should never happen with an // actual Cryptnos export file, but if the user accidentally tries to import // a DVD ISO or something, we don't want to blow up their machine. Bomb out // if we find out that the file is too large. if (fs.Length > (long)Int32.MaxValue) { fs.Close(); throw new ImportHandlerException("Import file too large to load into memory"); } // Read the entire contents of the file into memory: byte[] contents = new byte[(int)fs.Length]; fs.Read(contents, 0, contents.Length); fs.Close(); // Create our cipher in decryption mode: BufferedBlockCipher cipher = CreateCipher(password, false); // Create our plaintext container: byte[] plaintext = new byte[cipher.GetOutputSize(contents.Length)]; // Decrypt the data and create a memory stream so we can read from it: plaintext = cipher.DoFinal(contents); MemoryStream ms = new MemoryStream(plaintext); contents = null; // Define our XML reader settings: XmlReaderSettings xmlReaderSettings = new XmlReaderSettings(); // Note that we'll point to our local copy of the XSD, which should be in the // application directory: xmlReaderSettings.Schemas.Add("http://www.cryptnos.com/", Application.StartupPath + Char.ToString(System.IO.Path.DirectorySeparatorChar) + "cryptnos_export1.xsd"); // Validate against the schema. Invalid files will throw exceptions: xmlReaderSettings.ValidationType = ValidationType.Schema; xmlReaderSettings.ValidationEventHandler += new ValidationEventHandler(xmlReaderSettings_ValidationEventHandler); // Ignore unnecessary information: xmlReaderSettings.IgnoreComments = true; xmlReaderSettings.IgnoreWhitespace = true; // Close any other file/input streams when we close this one: xmlReaderSettings.CloseInput = true; // Create the XML reader. Note that we're reading from the memory stream // created from the decrypted file, then passing that through a gzip // decompressor before actually getting to the data. XmlReader xr = XmlReader.Create(new GZipStream(ms, CompressionMode.Decompress), xmlReaderSettings); // This forces us to go to the first element, which should be <cryptnos>. If // not, complain: xr.MoveToContent(); if (xr.Name != "cryptnos") { throw new ImportHandlerException("Invalid Cryptnos export file; expected <cryptnos> tag but got <" + xr.Name + ">"); } // At this point, things are looking good. We'll hopefully have sites we can // import now. Go ahead and create our List of SiteParameters so we can start // building it: List <SiteParameters> siteList = new List <SiteParameters>(); // Read the next element. This should be a <version> tag. If it is, make sure // it's a version we recognize. Otherwise, complain. xr.Read(); if (xr.NodeType != XmlNodeType.Element) { throw new ImportHandlerException("Invalid Cryptnos export file; expected an element, but got " + xr.NodeType.ToString()); } if (xr.Name.CompareTo("version") == 0) { xr.Read(); // Make sure this is a text value, then make sure it's the version // number we expect. This version of Cryptnos only accepts version // 1 of the Cryptnos export file format. if (xr.NodeType == XmlNodeType.Text && xr.Value != "1") { throw new ImportHandlerException("This Cryptnos export file appears to have been generated by a later version of Cryptnos and is incompatible with this version. (File format version was " + xr.Value + ".)"); } } else { throw new ImportHandlerException("Invalid Cryptnos export file; expected a <version> element, but got <" + xr.Name + ">"); } // Read on to the next tag: xr.Read(); while (xr.NodeType == XmlNodeType.EndElement) { xr.Read(); } if (xr.NodeType != XmlNodeType.Element) { throw new ImportHandlerException("Invalid Cryptnos export file; expected an element, but got " + xr.NodeType.ToString()); } // At this point, the next few tags should be the <generator> and/or <comment> // tags, neither of which we care about. Therefore, just read ahead until we // hit the <sites> tag, which is where we really want to go to next. while (xr.Name.CompareTo("siteCount") != 0) { do { xr.Read(); } while (xr.NodeType != XmlNodeType.Element); } // The next tag should be the <siteCount> tag. This contains the number of // site blocks in the file. This technically isn't necessary, but it was added // to improve reporting on Android, where performance is much more of an issue. // The main thing we'll worry about here is that (a) it's an integer greater // than zero and (b) the number of sites we eventually read must equal the // count listed here. if (xr.NodeType == XmlNodeType.Element && xr.Name.CompareTo("siteCount") == 0) { xr.Read(); if (xr.NodeType == XmlNodeType.Text) { siteCount = Int32.Parse(xr.Value); } if (siteCount <= 0) { throw new ImportHandlerException("Invalid Cryptnos export file; <siteCount> is " + siteCount.ToString()); } } // Read on to the next tag: xr.Read(); while (xr.NodeType == XmlNodeType.EndElement) { xr.Read(); } // Now we need to check to make sure we actually got a <sites> tag: if (xr.NodeType == XmlNodeType.Element && xr.Name.CompareTo("sites") == 0) { // Read the next tag. This should be a <site> tag and the beginning of a // site defintion. xr.Read(); while (xr.NodeType == XmlNodeType.Element && xr.Name.CompareTo("site") == 0) { // Create a new SiteParameters object to store the data we're about // to read. SiteParameters site = new SiteParameters(); // Try to read the <siteToken> tag: xr.Read(); if (xr.NodeType != XmlNodeType.Element) { throw new ImportHandlerException("Invalid Cryptnos export file; expected an element, but got " + xr.NodeType.ToString()); } if (xr.Name.CompareTo("siteToken") == 0) { xr.Read(); if (xr.NodeType == XmlNodeType.Text) { site.Site = xr.Value; } else { throw new ImportHandlerException("Invalid site token (" + xr.Value + ")"); } } else { throw new ImportHandlerException("Invalid Cryptnos export file; expected a <siteToken> element, but got <" + xr.Name + ">"); } do { xr.Read(); } while (xr.NodeType != XmlNodeType.Element); // Try to read the <hash> tag: if (xr.NodeType != XmlNodeType.Element) { throw new ImportHandlerException("Invalid Cryptnos export file; expected an element, but got " + xr.NodeType.ToString()); } if (xr.Name.CompareTo("hash") == 0) { xr.Read(); if (xr.NodeType == XmlNodeType.Text) { site.Hash = HashEngine.DisplayHashToHashEnumString(xr.Value); } else { throw new ImportHandlerException("Invalid hash token (" + xr.Value + ")"); } } else { throw new ImportHandlerException("Invalid Cryptnos export file; expected a <hash> element, but got <" + xr.Name + ">"); } do { xr.Read(); } while (xr.NodeType != XmlNodeType.Element); // Try to read the <iterations> tag: if (xr.NodeType != XmlNodeType.Element) { throw new ImportHandlerException("Invalid Cryptnos export file; expected an element, but got " + xr.NodeType.ToString()); } if (xr.Name.CompareTo("iterations") == 0) { xr.Read(); if (xr.NodeType == XmlNodeType.Text) { site.Iterations = Int32.Parse(xr.Value); } else { throw new ImportHandlerException("Invalid iterations token (" + xr.Value + ")"); } } else { throw new ImportHandlerException("Invalid Cryptnos export file; expected an <iterations> element, but got <" + xr.Name + ">"); } do { xr.Read(); } while (xr.NodeType != XmlNodeType.Element); // Try to read the <charTypes> tag: if (xr.NodeType != XmlNodeType.Element) { throw new ImportHandlerException("Invalid Cryptnos export file; expected an element, but got " + xr.NodeType.ToString()); } if (xr.Name.CompareTo("charTypes") == 0) { xr.Read(); if (xr.NodeType == XmlNodeType.Text) { site.CharTypes = Int32.Parse(xr.Value); } else { throw new ImportHandlerException("Invalid charTypes token (" + xr.Value + ")"); } } else { throw new ImportHandlerException("Invalid Cryptnos export file; expected a <charTypes> element, but got <" + xr.Name + ">"); } do { xr.Read(); } while (xr.NodeType != XmlNodeType.Element); // Try to read the <charLimit> tag: if (xr.NodeType != XmlNodeType.Element) { throw new ImportHandlerException("Invalid Cryptnos export file; expected an element, but got " + xr.NodeType.ToString()); } if (xr.Name.CompareTo("charLimit") == 0) { xr.Read(); if (xr.NodeType == XmlNodeType.Text) { // The character limit, unfortunately, is a bit inconsistent. // Obviously, we can't limit it to zero, as that will mean we // have an empty password. But the XML schema defines this as // only positive integers, so our built-in -1 value can't work. // So if we read a zero, convert it to -1 here. We'll do the // reverse when we write the file. site.CharLimit = Int32.Parse(xr.Value); if (site.CharLimit == 0) { site.CharLimit = -1; } } else { throw new ImportHandlerException("Invalid charLimit token (" + xr.Value + ")"); } } else { throw new ImportHandlerException("Invalid Cryptnos export file; expected a <charLimit> element, but got <" + xr.Name + ">"); } // The next item should be the closing element for <charLimit>, so // read it in and then read the next one. That should either be the // start element for the next <site> or the closing element for // <sites>. xr.Read(); if (xr.NodeType == XmlNodeType.EndElement) { xr.Read(); } else { throw new ImportHandlerException("Invalid Cryptnos export file; expected ending <charLimit> tag, got " + xr.NodeType.ToString()); } if (xr.NodeType == XmlNodeType.EndElement) { xr.Read(); } else { throw new ImportHandlerException("Invalid Cryptnos export file; expected ending <site> tag, got " + xr.NodeType.ToString()); } // We should now have a hopefully valid SiteParameters object. Add it // to the site list: siteList.Add(site); } // We get here, we've exhausted the <sites> block and all that should be // left will be closing tags. If we were going to be extremely thorough, // we should probably check these closing tags and make sure they're legit. // For now, we'll just assume there's nothing left to read. Close the // streams, free up memory, and return the list of read sites. xr.Close(); ms.Close(); ms.Dispose(); plaintext = null; if (siteList.Count != siteCount) { throw new ImportHandlerException("Invalid Cryptnos export file; File reported " + siteCount.ToString() + " sites in the file, but actually read " + siteList.Count.ToString()); } return(siteList); } else { throw new ImportHandlerException("Invalid Cryptnos export file; could not find <sites> tag"); } } catch (Exception ex) { throw ex; } }
/// <summary> /// Export a list of <see cref="SiteParameters"/> to an encrypted export file. Note /// that this method only exports to the newer XML-based cross-platform format, not /// the old platform specific format that is no longer supported. /// </summary> /// <param name="filename">A string containing the full path to the export file</param> /// <param name="password">A string containing the password used to encrypt the /// file</param> /// <param name="generator">A string containing the "generator" ID for the file, usually /// the full application name ("Cryptnos for Windows") and version number. If this /// value is null the generator tag will be omitted from the file.</param> /// <param name="comment">A string containing an optional comment. If this value is /// null the comment tag will be omitted from the file.</param> /// <param name="siteList">A <see cref="List"/> of <see cref="SiteParameters"/> to /// export</param> /// <exception cref="ArgumentException">Thrown if any required field (file name, /// password, or site list) is empty or null</exception> /// <exception cref="Exception">Thrown if anything blows up along the way</exception> public static void ExportToFile(string filename, string password, string generator, string comment, List <SiteParameters> siteList) { try { // A little bit of sanity checking. Make sure our required inputs are // not null or empty: if (String.IsNullOrEmpty(filename)) { throw new ArgumentException("File name is empty or null"); } if (String.IsNullOrEmpty(password)) { throw new ArgumentException("Password is empty or null"); } if (siteList == null || siteList.Count == 0) { throw new ArgumentException("Site parameter list is empty or null"); } // Set up the XML formatting options for our XML writer. I don't think these // guys are actually essential, given that our XML is not meant to be human // readable, but they seemed to work for Mandelbrot Madness! so I'll keep them // here. XmlWriterSettings xws = new XmlWriterSettings(); xws.Indent = true; xws.IndentChars = "\t"; xws.CloseOutput = true; xws.Encoding = Encoding.UTF8; // We won't be writing directly to a file, at least not yet. Create a memory // stream for us to write to initially, then open up the XML writer to point // to that stream. Note that we'll also gzip the XML as it goes into the // memory stream to compress it. MemoryStream ms = new MemoryStream(); XmlWriter xw = XmlWriter.Create(new GZipStream(ms, CompressionMode.Compress), xws); // Start writing out our XML by putting in the required headers. Note that // the <version> tag is required and for now must be 1, but the <generator> // and <comment> tags are technically optional. Generator is highly recommended, // however, as that helps us ID where the file came from. xw.WriteStartDocument(); xw.WriteStartElement("cryptnos", "http://www.cryptnos.com/"); xw.WriteElementString("version", "1"); if (!String.IsNullOrEmpty(generator)) { xw.WriteElementString("generator", generator); } if (!String.IsNullOrEmpty(comment)) { xw.WriteElementString("comment", comment); } xw.WriteElementString("siteCount", siteList.Count.ToString()); // Start writing out the <sites> tag xw.WriteStartElement("sites"); // Now step through each site parameter group and write out a <site> // tag to contain its data: foreach (SiteParameters site in siteList) { xw.WriteStartElement("site"); xw.WriteElementString("siteToken", site.Site); xw.WriteElementString("hash", HashEngine.HashEnumStringToDisplayHash(site.Hash)); xw.WriteElementString("iterations", site.Iterations.ToString()); xw.WriteElementString("charTypes", site.CharTypes.ToString()); if (site.CharLimit < 0) { xw.WriteElementString("charLimit", "0"); } else { xw.WriteElementString("charLimit", site.CharLimit.ToString()); } xw.WriteEndElement(); } // Close the <sites> tag: xw.WriteEndElement(); // Close the <cryptnos> tag and the rest of the document: xw.WriteEndElement(); xw.WriteEndDocument(); xw.Flush(); xw.Close(); ms.Flush(); ms.Close(); // Get the contents of the memory stream as raw bytes: byte[] plaintext = ms.ToArray(); // Create the cipher. Note that we're using the encryption // mode, and that we're passing in the password: BufferedBlockCipher cipher = CreateCipher(password, true); // Create our ciphertext container. Note that we call the // cipher's getOutputSize() method, which tells us how big // the resulting ciphertext should be. In practice, this // has always been the same size as the plaintext, but we // can't take that for granted. byte[] ciphertext = new byte[cipher.GetOutputSize(plaintext.Length)]; // Do the encyrption. Note that the .NET version is different from // the Java version. Here we've got it easy. The BC classes include // a simpler one-call DoFinal() method that does everything for us. ciphertext = cipher.DoFinal(plaintext); // Write the ciphertext to the export file: FileStream fs = new FileStream(filename, FileMode.Create); fs.Write(ciphertext, 0, ciphertext.Length); // Close up shop: fs.Flush(); fs.Close(); plaintext = null; ciphertext = null; } catch (Exception ex) { throw ex; } }