//  
//  This file is part of the OpenLink Software Virtuoso Open-Source (VOS)
//  project.
//  
//  Copyright (C) 1998-2012 OpenLink Software
//  
//  This project is free software; you can redistribute it and/or modify it
//  under the terms of the GNU General Public License as published by the
//  Free Software Foundation; only version 2 of the License, dated June 1991.
//  
//  This program is distributed in the hope that it will be useful, but
//  WITHOUT ANY WARRANTY; without even the implied warranty of
//  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
//  General Public License for more details.
//  
//  You should have received a copy of the GNU General Public License along
//  with this program; if not, write to the Free Software Foundation, Inc.,
//  51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
//  
//  
using System;
using System.Collections;
using System.ComponentModel;
using System.Windows.Forms;
using System.Security.Cryptography;
using System.Text;
using Microsoft.Web.Services2;
using Microsoft.Web.Services2.Security;
using Microsoft.Web.Services2.Security.Tokens;
using Microsoft.Web.Services2.Security.X509;
using System.Diagnostics;
using System.Xml.Serialization;
using System.Web.Services.Protocols;
using System.Web.Services;
using System.Runtime.InteropServices;

namespace OpenLink.Virtuoso.WSS.X509Signing
{
  /// <summary>
  /// This is a sample which allows to send
  /// a message signed with an X509 certificate.
  /// </summary>
  public class AddClient
    {
      int a, b;
      string url;

      AddClient(string[] args)
	{
	  Hashtable arguments = null;

	  ParseArguments(args, ref arguments);
	  if (arguments.Contains("?"))
	    {
	      Usage();
	    }

	  try
	    {
	      ConvertArgument(arguments, "a", ref a);
	      ConvertArgument(arguments, "b", ref b);
	      url = (string) arguments["url"];
	    }
	  catch (Exception)
	    {
	      Usage();
	      throw;
	    }
	}

      /// <summary>
      /// The main entry point for the application.
      /// </summary>
      [STAThread]
	  static void Main(string[] args)
	    {
	      AddClient client = null;
	      try
		{
		  client = new AddClient(args);
		}
	      catch (Exception)
		{
		  Console.WriteLine("\nOne or more of the required arguments are missing or incorrectly formed.");
		  return;
		}

	      try
		{
		  client.Run();
		}
	      catch (Exception ex)
		{
		  Error(ex);
		  return;
		}
	    }

      void Run()
	{
	  CallWebService(a, b, url);
	}

      void Usage()
	{
	  Console.WriteLine("Usage: X509SigningClient /a number /b number");
	  Console.WriteLine(" Required arguments:");
	  Console.WriteLine("    /url The Secure service endpoint");
	  Console.WriteLine("    /" + "a".PadRight(20) + "An integer. First number to add.");
	  Console.WriteLine("    /" + "b".PadRight(20) + "An integer. Second number to add.");
	}

      protected void ConvertArgument(Hashtable args, string argName, ref int arg)
	{
	  if (!args.Contains(argName))
	    {
	      throw new ArgumentException(argName);
	    }
	  arg = int.Parse(args[argName] as string);
	}

      protected void ConvertArgument(Hashtable args, string argName, ref long arg)
	{
	  if (!args.Contains(argName))
	    {
	      throw new ArgumentException(argName);
	    }
	  arg = long.Parse(args[argName] as string);
	}

      protected void ConvertArgument(Hashtable args, string argName, ref bool arg)
	{
	  if (!args.Contains(argName))
	    {
	      throw new ArgumentException(argName);
	    }
	  arg = bool.Parse(args[argName] as string);
	}

      protected string GetOption(string arg)
	{
	  if (!arg.StartsWith("/") && !arg.StartsWith("-"))
	    return null;
	  return arg.Substring(1);
	}

      protected void ParseArguments(string[] args, ref Hashtable table)
	{
	  table = new Hashtable();
	  int index = 0;
	  while (index < args.Length)
	    {
	      string option = GetOption(args[index]);
	      if (option != null)
		{
		  if (index+1 < args.Length && GetOption(args[index+1]) == null)
		    {
		      table[option] = args[++index];
		    }
		  else
		    {
		      table[option] = "true";
		    }
		}
	      index++;
	    }
	}

      protected static void Error(Exception e)
	{
	  StringBuilder sb = new StringBuilder();
	  if (e is System.Web.Services.Protocols.SoapException)
	    {
	      System.Web.Services.Protocols.SoapException se = e as System.Web.Services.Protocols.SoapException;
	      sb.Append("SOAP-Fault code: " + se.Code.ToString());
	      sb.Append("\n");
	    }
	  if (e != null)
	    {
	      sb.Append(e.ToString());
	    }
	  Console.WriteLine("*** Exception Raised ***");
	  Console.WriteLine(sb.ToString());
	  Console.WriteLine("************************");
	}

      private void CallWebService(int a, int b, string url)
	{
	  // Instantiate an instance of the web service proxy
	  AddNumbers serviceProxy = new AddNumbers();
	  SoapContext requestContext = serviceProxy.RequestSoapContext;

	  // Get our security token
	  X509SecurityToken token = GetSecurityToken();
	  if (token == null)
	    throw new ApplicationException("No key provided for signature.");

	  // Add the signature element to a security section on the request
	  // to sign the request
	  requestContext.Security.Tokens.Add(token);
	  requestContext.Security.Elements.Add(new MessageSignature(token));

	  //requestContext.Timestamp.Ttl = 6000000;
	  // Call the service
	  if (url != null)
	    serviceProxy.Url = url;
	  Console.WriteLine("Calling {0}", serviceProxy.Url);
	  int sum = serviceProxy.AddInt(a, b);

	  // Success!
	  string message = string.Format("{0} + {1} = {2}", a, b, sum);
	  Console.WriteLine("Web Service returned: {0}", message);
	}

      /// <summary>
      /// Gets the security token for signing messages.
      /// </summary>
      /// <returns>Returns </returns>
      public X509SecurityToken GetSecurityToken()
	{
	  X509SecurityToken securityToken;
	  //
	  // open the current user's certificate store
	  //
	  X509CertificateStore store = X509CertificateStore.CurrentUserStore(X509CertificateStore.MyStore);
	  bool open = store.OpenRead();

	  try
	    {
	      //
	      // Open a dialog to allow user to select the certificate to use
	      //
	      StoreDialog dialog = new StoreDialog(store);
	      X509Certificate cert = dialog.SelectCertificate(IntPtr.Zero, "Select Certificate", "Choose a Certificate below for signing.");
	      if (cert == null)
		{
		  throw new ApplicationException("You chose not to select an X509 certificate for signing your messages.");
		}
	      else if (!cert.SupportsDigitalSignature)
		{
		  throw new ApplicationException("The certificate must support digital signatures and have a private key available.");
		}
	      else
		{
		  securityToken = new X509SecurityToken(cert);
		}
	    }
	  finally
	    {
	      if (store != null) { store.Close(); }
	    }
	  return securityToken;
	}

      class StoreDialog {

	X509CertificateStore store;

	public StoreDialog(X509CertificateStore store)
	  {
	    this.store = store;
	  }

	static bool IsWinXP()
	  {
	    OperatingSystem os = Environment.OSVersion;
	    Version v = os.Version;

	    if (os.Platform == PlatformID.Win32NT && v.Major >= 5 && v.Minor >= 1)
	      {
		return true;
	      }

	    return false;
	  }

	/// <summary>
	/// Displays a dialog that can be used to select a certificate from the store.
	/// </summary>
	public X509Certificate SelectCertificate(IntPtr hwnd, string title, string displayString)
	  {
	    if (store.Handle == IntPtr.Zero)
	      throw new InvalidOperationException("Store is not open");

	    if (IsWinXP())
	      {
		IntPtr certPtr = CryptUIDlgSelectCertificateFromStore(store.Handle, hwnd, title, displayString, 0/*dontUseColumn*/, 0 /*flags*/, IntPtr.Zero);
		if (certPtr != IntPtr.Zero)
		  {
		    return new X509Certificate(certPtr);
		  }
	      }
	    else
	      {
		SelectCertificateDialog dlg = new SelectCertificateDialog(store);
		if (dlg.ShowDialog() != DialogResult.OK)
		  {
		    return null;
		  }
		else
		  {
		    return dlg.Certificate;
		  }
	      }

	    return null;
	  }

	[DllImport("cryptui", CharSet=CharSet.Unicode, SetLastError=true)]
	    internal extern static IntPtr CryptUIDlgSelectCertificateFromStore(IntPtr hCertStore, IntPtr hwnd, string pwszTitle, string pwszDisplayString, uint dwDontUseColumn, uint dwFlags, IntPtr pvReserved);
      }

      /// <summary>
      /// SelectCertificateDialog.
      /// </summary>
      class SelectCertificateDialog : System.Windows.Forms.Form
	{
	  /// <summary>
	  /// Required designer variable.
	  /// </summary>
	  private System.Windows.Forms.Button _okBtn;
	  private System.Windows.Forms.Button _cancelBtn;

	  private X509CertificateStore _store;
	  private System.Windows.Forms.ListView _certList;
	  private System.Windows.Forms.ColumnHeader _certName;
	  private X509Certificate _certificate = null;

	  public SelectCertificateDialog(X509CertificateStore store) : base()
	    {
	      _store = store;

	      // Required for Windows Form Designer support
	      //
	      InitializeComponent();
	    }

	  public X509Certificate Certificate
	    {
	      get
		{
		  return _certificate;
		}
	    }

	  /// <summary>
	  /// Required method for Designer support - do not modify
	  /// the contents of this method with the code editor.
	  /// </summary>
	  private void InitializeComponent()
	    {
	      this._okBtn = new System.Windows.Forms.Button();
	      this._cancelBtn = new System.Windows.Forms.Button();
	      this._certList = new System.Windows.Forms.ListView();
	      this._certName = new System.Windows.Forms.ColumnHeader();
	      this.SuspendLayout();
	      //
	      // _okBtn
	      //
	      this._okBtn.Location = new System.Drawing.Point(96, 232);
	      this._okBtn.Name = "_okBtn";
	      this._okBtn.TabIndex = 1;
	      this._okBtn.Text = "OK";
	      this._okBtn.Click += new System.EventHandler(this.OkBtn_Click);
	      //
	      // _cancelBtn
	      //
	      this._cancelBtn.DialogResult = System.Windows.Forms.DialogResult.Cancel;
	      this._cancelBtn.Location = new System.Drawing.Point(192, 232);
	      this._cancelBtn.Name = "_cancelBtn";
	      this._cancelBtn.TabIndex = 2;
	      this._cancelBtn.Text = "Cancel";
	      this._cancelBtn.Click += new System.EventHandler(this.CancelBtn_Click);
	      //
	      // _certList
	      //
	      this._certList.Columns.AddRange(new System.Windows.Forms.ColumnHeader[]{
		  this._certName});
	      this._certList.Dock = System.Windows.Forms.DockStyle.Top;
	      this._certList.FullRowSelect = true;
	      this._certList.MultiSelect = false;
	      this._certList.Name = "_certList";
	      this._certList.Size = new System.Drawing.Size(292, 176);
	      this._certList.TabIndex = 3;
	      this._certList.View = System.Windows.Forms.View.Details;
	      //
	      // _certName
	      //
	      this._certName.Text = "Name";
	      this._certName.Width = 92;
	      //
	      // SelectCertificateDialog
	      //
	      this.AcceptButton = this._okBtn;
	      this.AutoScaleBaseSize = new System.Drawing.Size(5, 13);
	      this.CancelButton = this._cancelBtn;
	      this.ClientSize = new System.Drawing.Size(292, 266);
	      this.Controls.AddRange(new System.Windows.Forms.Control[]{
		  this._certList,
		  this._cancelBtn,
		  this._okBtn});
	      this.Name = "SelectCertificateDialog";
	      this.Text = "SelectCertificateDialog";
	      this.ResumeLayout(false);

	    }

	  protected override void OnLoad(EventArgs e)
	    {
	      base.OnLoad(e);

	      if (_store == null)
		{
		  throw new Exception("No store to open");
		}

	      if (_store.Handle == IntPtr.Zero)
		{
		  throw new Exception("Store not open for reading");
		}

	      X509CertificateCollection coll = _store.Certificates;

	      foreach(X509Certificate cert in coll)
		{
		  ListViewItem item = new ListViewItem(cert.GetName());
		  this._certList.Items.Add(item);
		}
	    }

	  private void OkBtn_Click(object sender, System.EventArgs e)
	    {
	      _certificate = null;

	      if (_certList.SelectedItems != null && _certList.SelectedItems.Count == 1)
		{
		  X509CertificateCollection coll = _store.FindCertificateBySubjectName(_certList.SelectedItems[0].Text);
		  if (coll != null && coll.Count == 1)
		    {
		      _certificate = coll[0] as X509Certificate;
		    }
		}

	      this.Close();
	      this.DialogResult = DialogResult.OK;
	    }

	  private void CancelBtn_Click(object sender, System.EventArgs e)
	    {
	      _certificate = null;
	    }
	}

      //
      // Web Service Proxy class
      //
      [System.Diagnostics.DebuggerStepThroughAttribute()]
	  [System.ComponentModel.DesignerCategoryAttribute("code")]
	  [System.Web.Services.WebServiceBindingAttribute(Name="VirtuosoWSSecure", Namespace="http://temp.uri/")]

	  public class AddNumbers : Microsoft.Web.Services2.WebServicesClientProtocol {

	    /// <remarks/>
	    public AddNumbers() {
	      this.Url = "http://localhost:8890/SecureWebServices";
	    }

	    /// <remarks/>
	    [System.Web.Services.Protocols.SoapRpcMethodAttribute("http://temp.uri/#AddInt", RequestNamespace="http://temp.uri/", ResponseNamespace="http://temp.uri/")]
            [return: System.Xml.Serialization.SoapElementAttribute("CallReturn")]
		public int AddInt(int a, int b) {
		  object[] results = this.Invoke("AddInt", new object[] {
		      a,
		      b});
		  return ((int)(results[0]));
		}

	    /// <remarks/>
	    public System.IAsyncResult BeginAddInt(int a, int b, System.AsyncCallback callback, object asyncState) {
	      return this.BeginInvoke("AddInt", new object[] {
		  a,
		  b}, callback, asyncState);
	    }

	    /// <remarks/>
	    public int EndAddInt(System.IAsyncResult asyncResult) {
	      object[] results = this.EndInvoke(asyncResult);
	      return ((int)(results[0]));
	    }
	  }
    }
}
