/// <summary> /// Get a key for a new database. The user is first asked for the key filename (by default /// same location and base filename as the database, but with a different extension). Next, /// the user selects what certificates to encrypt for. A 256-byte random key is generated. /// </summary> /// <param name="strPath">Full filename of the database file.</param> /// <returns>A byte array with the key, or null if an error occurs. If an error occurs, user is /// notified of the error.</returns> byte[] GetNewKey(string strPath) { MessageBox.Show(Res.str(Res.STR_ENC_KEY_INTRO), Res.str(Res.STR_APP_TITLE), MessageBoxButtons.OK, MessageBoxIcon.Information); SaveFileDialog sfd = UIUtil.CreateSaveFileDialog(Res.str(Res.STR_CREATE_KEY_FILE), UrlUtil.StripExtension(UrlUtil.GetFileName(strPath)) + "." + CertProtKeyFileExtension, UIUtil.CreateFileTypeFilter(CertProtKeyFileExtension, Res.str(Res.STR_CERT_PROT_KEY_FILE), true), 1, CertProtKeyFileExtension, true); if (sfd.ShowDialog() != DialogResult.OK) { return(null); } CryptoRandom rnd = CryptoRandom.Instance; byte[] key = rnd.GetRandomBytes(256); try { byte[] p7m = SelectCertsAndEncryptMsg(key); if (p7m == null) { return(null); } BinaryWriter writer = new BinaryWriter(File.Open(sfd.FileName, FileMode.Create)); writer.Write(p7m); writer.Close(); return(key); } catch (SystemException e) { String msg = String.Format(Res.str(Res.STR_ERR_ENCRYPTING_KEY), e.Message); MessageBox.Show(msg, Res.AppTitle); return(null); } }
/// <summary> /// Encrypt a byte array for a user-selected set of encryption certificates. More than 1 certificate /// can be selected. Certificates can be selected from the "My" store and the "AddressBook" store. /// </summary> /// <param name="key">Data to encrypt</param> /// <returns>Encrypted blob</returns> private byte[] SelectCertsAndEncryptMsg(byte[] data) { X509Store addrBookStore = new X509Store(StoreName.AddressBook, StoreLocation.CurrentUser); addrBookStore.Open(OpenFlags.ReadOnly); X509Store myStore = new X509Store(StoreName.My, StoreLocation.CurrentUser); myStore.Open(OpenFlags.ReadOnly); X509Certificate2Collection allCerts = (X509Certificate2Collection)addrBookStore.Certificates; allCerts.AddRange(myStore.Certificates); addrBookStore.Close(); myStore.Close(); X509Certificate2Collection fcollection = (X509Certificate2Collection)allCerts.Find( X509FindType.FindByTimeValid, DateTime.Now, false); fcollection = fcollection.Find(X509FindType.FindByKeyUsage, X509KeyUsageFlags.KeyEncipherment, false); X509Certificate2Collection scollection = X509Certificate2UI.SelectFromCollection(fcollection, Res.str(Res.STR_SELECT_CERT), Res.str(Res.STR_SELECT_CERT_LONG), X509SelectionFlag.MultiSelection); if (scollection == null || scollection.Count < 1) { return(null); } // validate certificates X509Chain chain = new X509Chain(); chain.ChainPolicy.RevocationMode = X509RevocationMode.Online; X509Certificate2Collection toRemove = new X509Certificate2Collection(); foreach (X509Certificate2 cert in scollection) { Boolean chainRc = false; try { Cursor.Current = Cursors.WaitCursor; chainRc = chain.Build(cert); } finally { Cursor.Current = Cursors.Default; } if (!chainRc) { // certificate is invalid ... keep it? String certInfoTemplate = String.Format(Res.str(Res.STR_CERT_INFO_TEMPLATE), cert.Subject, cert.Issuer, cert.GetSerialNumberString()); String warning = String.Format(Res.str(Res.STR_CERT_DIDNT_VALIDATE_CONTINUE), certInfoTemplate); StringBuilder reason = new StringBuilder(); for (int index = 0; index < chain.ChainStatus.Length; index++) { reason.AppendLine(chain.ChainStatus[index].StatusInformation); } DialogResult decision = MessageBox.Show(warning + "\n\n" + reason.ToString(), Res.str(Res.STR_APP_TITLE), MessageBoxButtons.YesNoCancel, MessageBoxIcon.Exclamation, MessageBoxDefaultButton.Button3); if (decision == DialogResult.Cancel) { return(null); } if (decision == DialogResult.No) { toRemove.Insert(0, cert); } } } foreach (X509Certificate2 cert in toRemove) { scollection.Remove(cert); } if (scollection.Count < 1) { MessageBox.Show(Res.str(Res.STR_NO_CERTS_LEFT), Res.str(Res.STR_APP_TITLE)); return(null); } // check to make sure the user can decrypt the key bool havePrivateKey = false; foreach (X509Certificate2 cert in scollection) { havePrivateKey |= cert.HasPrivateKey; } if (!havePrivateKey) { DialogResult decision = MessageBox.Show(Res.str(Res.STR_NO_PRIV_KEY_IN_CERTS), Res.str(Res.STR_APP_TITLE), MessageBoxButtons.YesNo, MessageBoxIcon.Exclamation, MessageBoxDefaultButton.Button2); if (decision == DialogResult.No) { return(null); } } byte[] p7m = CryptoCmsTools.EncryptMsg(data, scollection); return(p7m); }
/// <summary> /// Get a key for an existing database. First, the key file is located, either because its location /// and filename are the same as the database path (with the exception of the extension), or the user /// is asked. Then, the key file is decrypted using a private key. /// </summary> /// <param name="strPath">Full filename of the database file.</param> /// <returns>A byte array with the key, or null if an error occurs. If an error occurs, user is /// notified of the error.</returns> byte[] GetExistingKey(IOConnectionInfo ioc) { Stream stream = null; try { string newpath = UrlUtil.StripExtension(ioc.Path) + "." + CertProtKeyFileExtension; IOConnectionInfo keyIoc = ioc.CloneDeep(); keyIoc.Path = newpath; stream = IOConnection.OpenRead(keyIoc); } catch (Exception) { // strPath may be a URL (even if IsLocalFile returns true?), // whatever the reason, fall through and the user can pick a // local file as the key file } if (stream == null || !stream.CanRead) { // fall back on opening a local file // FUTURE ENHANCEMENT: allow user to enter a URL and name/pwd as well OpenFileDialog ofd = UIUtil.CreateOpenFileDialog(Res.str(Res.STR_OPEN_KEY_FILE), UIUtil.CreateFileTypeFilter(CertProtKeyFileExtension, Res.str(Res.STR_CERT_PROT_KEY_FILE), true), 1, CertProtKeyFileExtension, false /* multi-select */, true); if (ofd.ShowDialog() != DialogResult.OK) { return(null); } stream = IOConnection.OpenRead(IOConnectionInfo.FromPath(ofd.FileName)); } try { BinaryReader reader = new BinaryReader(stream); byte[] p7m = reader.ReadBytes(MAX_KEY_FILE_LENGTH); // URL streams don't support seeking, and so Position doesn't work //bool tooBig = stream.Position >= MAX_KEY_FILE_LENGTH; bool tooBig = p7m.Length >= MAX_KEY_FILE_LENGTH; reader.Close(); if (tooBig) { MessageBox.Show(Res.str(Res.STR_KEY_FILE_TOO_BIG), Res.AppTitle); return(null); } Cursor.Current = Cursors.WaitCursor; return(CryptoCmsTools.DecryptMsg(p7m)); } catch (SystemException ex) // covers IOException and CryptographicException { String msg = String.Format(Res.str(Res.STR_ERR_DECRYPTING_KEY), ex.ToString()); //String msg = String.Format(Res.str(Res.STR_ERR_DECRYPTING_KEY), getCryptoExceptionDetails(ex)); MessageBox.Show(msg, Res.AppTitle); return(null); } finally { Cursor.Current = Cursors.Default; } }