/// <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> /// Given an open registry key and a site key value, read the site parameters from /// the registry and return a SiteParameters object. /// </summary> /// <param name="registryKey">An open registry key</param> /// <param name="siteKey">A site key string</param> /// <returns>A SiteParameters object, or null on failure</returns> public static SiteParameters ReadFromRegistry(RegistryKey registryKey, string siteKey) { // Asbestos underpants: try { // This only works if the registry key is open and the site key is // something meaningful: if (registryKey != null && !String.IsNullOrEmpty(siteKey)) { // Look for the site key value in the registry key and convert it from // Base64 to a byte array: byte[] encryptedParams = (byte[])registryKey.GetValue(siteKey); // Set up our decryption engine. Create the Rijndael object, its // encryption key, and its initialization vector. RijndaelManaged rijndael = new RijndaelManaged(); if (rijndael.ValidKeySize(256)) { rijndael.KeySize = 256; } rijndael.Padding = PaddingMode.PKCS7; byte[] cryptKey = GenerateEncryptionKey(siteKey); byte[] iv = GenerateIV(rijndael.BlockSize / 8, siteKey); // Decrypt the raw bytes read from the registry: ICryptoTransform decryptor = rijndael.CreateDecryptor(cryptKey, iv); MemoryStream ms = new MemoryStream(encryptedParams); CryptoStream cs = new CryptoStream(ms, decryptor, CryptoStreamMode.Read); byte[] decryptedBytes = new byte[encryptedParams.Length]; cs.Read(decryptedBytes, 0, decryptedBytes.Length); cs.Close(); ms.Close(); // Reset the memory stream to read the raw bytes and use a binary // formatter to deserialize the object: ms = new MemoryStream(decryptedBytes); BinaryFormatter bf = new BinaryFormatter(); SiteParameters sp = (SiteParameters)bf.Deserialize(ms); return(sp); } // If the registry key wasn't open or the site key wasn't meaningful, there's // nothing to do: else { return(null); } } // If anything blows up, don't return anything we can use: catch { return(null); } }
/// <summary> /// Given a site name, save its parameter information to the registry /// </summary> /// <param name="site">The site to save</param> private void SaveSiteParams(string site) { // Asbestos underpants: try { // Only bother continuing if the registry is open and the site isn't empty: if (CryptnosRegistryKeyOpen() && !String.IsNullOrEmpty(site)) { // Try to parse the iteration count text box value. If it's not a positive // integer greater than zero, complain. What I'm worried about here is // that I think this gets called when the application closes, so there's // a chance we could lose our site params if this fails. int iterations = 1; bool parsedIterations = Int32.TryParse(txtIterations.Text, out iterations); if (!parsedIterations || iterations < 1) { MessageBox.Show("The iteration count must be a positive integer " + "greater than zero.", "Error", MessageBoxButtons.OK, MessageBoxIcon.Error); txtIterations.Focus(); } else { // Using the GUI inputs, generate a SiteParameters object. Note that for // the character limit we'll pass in a -1 if the drop-down is actually // set to the first item ("None"), while the rest go in pretty much as // is. SiteParameters siteParams = new SiteParameters(cbSites.Text, cbCharTypes.SelectedIndex, cbCharLimit.SelectedIndex == 0 ? -1 : cbCharLimit.SelectedIndex, HashEngine.DisplayHashToHashEnumString((string)cbHashes.SelectedItem), iterations); // Attempt to save the site parameters to the registry. If that works, // proceed: if (siteParams.SaveToRegistry(siteParamsKey)) { // Set the last site value to this site: CryptnosSettings.SetValue("LastSite", SiteParameters.GenerateKeyFromSite(cbSites.Text), RegistryValueKind.String); // If the site doesn't exist in the drop-down list, add it now. That way // it can be quickly selected again. To do that, we'll have to repopulate // the entire list to make sure the it gets sorted alphabetically. Then // we'll set the selected value back to the new item's site token. if (cbSites.Items.IndexOf(cbSites.Text) == -1) { PopulateSitesDropdown(); cbSites.SelectedValue = cbSites.Text; } // Since there are sites in the list now, enable the Forget and Export // buttons: btnForgetAll.Enabled = true; btnExport.Enabled = true; btnForget.Enabled = true; } } } } // Silently ignore errors: catch { } }
/// <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; } }