/* ======================================================================== * Copyright (c) 2005-2020 The OPC Foundation, Inc. All rights reserved. * * OPC Foundation MIT License 1.00 * * Permission is hereby granted, free of charge, to any person * obtaining a copy of this software and associated documentation * files (the "Software"), to deal in the Software without * restriction, including without limitation the rights to use, * copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following * conditions: * * The above copyright notice and this permission notice shall be * included in all copies or substantial portions of the Software. * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR * OTHER DEALINGS IN THE SOFTWARE. * * The complete license agreement can be found here: * http://opcfoundation.org/License/MIT/1.00/ * ======================================================================*/ using System; using System.Collections.Generic; using System.Text; using System.Windows.Forms; using System.Security.Cryptography.X509Certificates; using System.Reflection; using System.Xml; using System.IO; using System.Runtime.Serialization; using System.Threading.Tasks; using Opc.Ua.Security.Certificates; namespace Opc.Ua.Client.Controls { /// /// Displays a list of certificates. /// public partial class CertificateListCtrl : Opc.Ua.Client.Controls.BaseListCtrl { #region Constructors /// /// Constructs the object. /// public CertificateListCtrl() { InitializeComponent(); SetColumns(m_ColumnNames); } #endregion #region Private Fields // The columns to display in the control. private readonly object[][] m_ColumnNames = new object[][] { new object[] { "Name", HorizontalAlignment.Left, null }, new object[] { "Type", HorizontalAlignment.Left, null }, new object[] { "Private Key", HorizontalAlignment.Center, null }, new object[] { "Domains", HorizontalAlignment.Left, null }, new object[] { "Uri ", HorizontalAlignment.Left, null }, new object[] { "Valid Until", HorizontalAlignment.Left, null } }; private CertificateStoreIdentifier m_storeId; private CertificateIdentifierCollection m_certificates; private IList m_thumbprints; private List m_items; #endregion #region Public Interface /// /// The currently selected certificate. /// public X509Certificate2 SelectedCertificate { get { return SelectedTag as X509Certificate2; } } /// /// Gets a value indicating whether this instance is empty store. /// public bool IsEmptyStore { get { if (m_items == null || m_items.Count == 0) { return true; } return false; } } /// /// Removes all items in the list. /// internal void Clear() { ItemsLV.Items.Clear(); Instructions = String.Empty; AdjustColumns(); } /// /// Sets the filter. /// /// The filter. internal void SetFilter(CertificateListFilter filter) { if (m_items == null || m_items.Count == 0) { return; } if (ItemsLV.View == View.List) { ItemsLV.Items.Clear(); ItemsLV.View = View.Details; } for (int ii = 0; ii < m_items.Count; ii++) { ListViewItem item = m_items[ii]; X509Certificate2 certificate = item.Tag as X509Certificate2; if (certificate == null) { continue; } if (item.ListView != null) { if (!filter.Match(certificate)) { item.Remove(); } } else { if (filter.Match(certificate)) { ItemsLV.Items.Add(item); } } } if (ItemsLV.Items.Count == 0) { Instructions = "No certificates meet the current filter criteria."; AdjustColumns(); return; } } /// /// Displays the applications in the control. /// internal void Initialize(CertificateIdentifierCollection certificates) { ItemsLV.Items.Clear(); m_certificates = certificates; if (m_certificates == null || m_certificates.Count == 0) { Instructions = "No certificates are in the store."; AdjustColumns(); return; } // display the list. foreach (CertificateIdentifier certificate in certificates) { AddItem(certificate); } // save the unfiltered list. m_items = new List(ItemsLV.Items.Count); foreach (ListViewItem item in ItemsLV.Items) { m_items.Add(item); } AdjustColumns(); } /// /// Displays the applications in the control. /// internal async Task Initialize(CertificateStoreIdentifier id, IList thumbprints) { ItemsLV.Items.Clear(); m_storeId = id; m_thumbprints = thumbprints; if (m_storeId == null || String.IsNullOrEmpty(m_storeId.StoreType) || String.IsNullOrEmpty(m_storeId.StorePath)) { Instructions = "No certificates are in the store."; AdjustColumns(); return ; } try { // get the store. using (ICertificateStore store = m_storeId.OpenStore()) { // only show certificates with the specified thumbprint. if (thumbprints != null) { Instructions = "None of the selected certificates can be found in the store."; foreach (string thumbprint in thumbprints) { X509Certificate2Collection certificates = await store.FindByThumbprint(thumbprint); if (certificates.Count > 0) { AddItem(certificates[0]); } } } // show all certificates. else { Instructions = "No certificates are in the store."; X509Certificate2Collection certificates = await store.Enumerate(); foreach (X509Certificate2 certificate in certificates) { AddItem(certificate); } } } } catch (Exception e) { Instructions = "An error occurred opening the store: " + e.Message; } // save the unfiltered list. m_items = new List(ItemsLV.Items.Count); foreach (ListViewItem item in ItemsLV.Items) { m_items.Add(item); } AdjustColumns(); } #endregion #region Overridden Methods /// /// Handles a double click event. /// protected override void PickItems() { base.PickItems(); ViewMI_Click(this, null); } /// /// Updates an item in the view. /// protected override void UpdateItem(ListViewItem listItem, object item) { X509Certificate2 certificate = item as X509Certificate2; if (certificate == null) { base.UpdateItem(listItem, item); return; } listItem.SubItems[0].Text = null; listItem.SubItems[1].Text = null; listItem.SubItems[2].Text = null; listItem.SubItems[3].Text = null; listItem.SubItems[4].Text = null; listItem.SubItems[5].Text = null; if (certificate != null) { List fields = X509Utils.ParseDistinguishedName(certificate.Subject); for (int ii = 0; ii < fields.Count; ii++) { if (fields[ii].StartsWith("CN=")) { listItem.SubItems[0].Text = fields[ii].Substring(3); } if (fields[ii].StartsWith("DC=")) { listItem.SubItems[1].Text = fields[ii].Substring(3); } } if (String.IsNullOrEmpty(listItem.SubItems[0].Text)) { listItem.SubItems[0].Text = String.Format("{0}", certificate.Subject); } // determine certificate type. foreach (X509Extension extension in certificate.Extensions) { X509BasicConstraintsExtension basicContraints = extension as X509BasicConstraintsExtension; if (basicContraints != null) { if (basicContraints.CertificateAuthority) { listItem.SubItems[1].Text = "CA"; } else { listItem.SubItems[1].Text = "End-Entity"; } break; } } // check if a private key is available. if (certificate.HasPrivateKey) { listItem.SubItems[2].Text = "Yes"; } else { listItem.SubItems[2].Text = "No"; } // look up domains. IList domains = X509Utils.GetDomainsFromCertficate(certificate); StringBuilder buffer = new StringBuilder(); for (int ii = 0; ii < domains.Count; ii++) { if (buffer.Length > 0) { buffer.Append(";"); } buffer.Append(domains[ii]); } listItem.SubItems[3].Text = buffer.ToString(); listItem.SubItems[4].Text = X509Utils.GetApplicationUriFromCertificate(certificate); listItem.SubItems[5].Text = String.Format("{0:yyyy-MM-dd}", certificate.NotAfter); } listItem.ImageKey = GuiUtils.Icons.Certificate; listItem.Tag = item; } /// /// Enables the menu items based on the current selection. /// protected override void EnableMenuItems(ListViewItem clickedItem) { base.EnableMenuItems(clickedItem); DeleteMI.Enabled = ItemsLV.SelectedItems.Count > 0; X509Certificate2 certificate = SelectedTag as X509Certificate2; if (certificate != null) { ViewMI.Enabled = true; CopyMI.Enabled = true; } IDataObject clipboardData = Clipboard.GetDataObject(); if (clipboardData.GetDataPresent(DataFormats.Text)) { PasteMI.Enabled = true; } } #endregion private async void ViewMI_Click(object sender, EventArgs e) { try { X509Certificate2 certificate = SelectedTag as X509Certificate2; if (certificate != null) { CertificateIdentifier id = new CertificateIdentifier(); id.Certificate = certificate; if (m_storeId != null) { id.StoreType = m_storeId.StoreType; id.StorePath = m_storeId.StorePath; } await new ViewCertificateDlg().ShowDialog(id); } } catch (Exception exception) { GuiUtils.HandleException(this.Text, MethodBase.GetCurrentMethod(), exception); } } private async void DeleteMI_Click(object sender, EventArgs e) { try { if (ItemsLV.SelectedItems.Count < 1) { return; } DialogResult result = MessageBox.Show( "Are you sure you wish to delete the certificates from the store?", "Delete Certificate", MessageBoxButtons.YesNo, MessageBoxIcon.Exclamation); if (result != DialogResult.Yes) { return; } // remove the certificates. List itemsToDelete = new List(); bool yesToAll = false; using (ICertificateStore store = m_storeId.OpenStore()) { for (int ii = 0; ii < ItemsLV.SelectedItems.Count; ii++) { X509Certificate2 certificate = ItemsLV.SelectedItems[ii].Tag as X509Certificate2; // check for private key. X509Certificate2Collection certificate2 = await store.FindByThumbprint(certificate.Thumbprint); if (!yesToAll && (certificate2.Count > 0) && certificate2[0].HasPrivateKey) { StringBuilder buffer = new StringBuilder(); buffer.Append("Certificate '"); buffer.Append(certificate2[0].Subject); buffer.Append("'"); buffer.Append("Deleting it may cause applications to stop working."); buffer.Append("\r\n"); buffer.Append("\r\n"); buffer.Append("Are you sure you wish to continue?."); DialogResult yesno = new YesNoDlg().ShowDialog(buffer.ToString(), "Delete Private Key", true); if (yesno == DialogResult.No) { continue; } yesToAll = yesno == DialogResult.Retry; } if (certificate != null) { await store.Delete(certificate.Thumbprint); itemsToDelete.Add(ItemsLV.SelectedItems[ii]); } } } // remove the items. foreach (ListViewItem itemToDelete in itemsToDelete) { itemToDelete.Remove(); } } catch (Exception exception) { GuiUtils.HandleException(this.Text, MethodBase.GetCurrentMethod(), exception); await Initialize(m_storeId, m_thumbprints); } } private void CopyMI_Click(object sender, EventArgs e) { try { X509Certificate2 certificate = SelectedTag as X509Certificate2; if (certificate == null) { return; } StringBuilder builder = new StringBuilder(); XmlWriter writer = XmlWriter.Create(builder); try { DataContractSerializer serializer = new DataContractSerializer(typeof(CertificateIdentifier)); CertificateIdentifier id = new CertificateIdentifier(); id.Certificate = certificate; serializer.WriteObject(writer, id); } finally { writer.Close(); } ClipboardHack.SetData(DataFormats.Text, builder.ToString()); } catch (Exception exception) { GuiUtils.HandleException(this.Text, MethodBase.GetCurrentMethod(), exception); } } private void PasteMI_Click(object sender, EventArgs e) { try { string xml = (string)ClipboardHack.GetData(DataFormats.Text); if (String.IsNullOrEmpty(xml)) { return; } // deserialize the data. CertificateIdentifier id = null; using (XmlTextReader reader = new XmlTextReader(new StringReader(xml))) { DataContractSerializer serializer = new DataContractSerializer(typeof(CertificateIdentifier)); id = (CertificateIdentifier)serializer.ReadObject(reader, false); } if (id.Certificate != null) { using (ICertificateStore store = m_storeId.OpenStore()) { store.Add(id.Certificate); } AddItem(id.Certificate); } } catch (Exception exception) { GuiUtils.HandleException(this.Text, MethodBase.GetCurrentMethod(), exception); } } } }