How to verify signatures using a temporary keyset in .NET
Hi all,
Some time ago a customer of mine had issues to verify signatures with RSACryptoServiceProvider when users had a mandatory and/or roaming profile, as he was getting the following exception:
"System.Security.Cryptography.CryptographicException: Cryptographic Service Provider (CSP) for this implementation could not be acquired".
I already talked about this: RSACryptoServiceProvider fails when used with mandatory profiles. There I mentioned the following workaround:
The way to i.e. verify signatures using temporary keysets in .NET would be to use CryptoAPI directly through P/Invoke (Platform Invoke), instead of using RSACryptoServiceProvider.
So I created the following sample that uses CryptoAPI via P/Invoke to be able to verify signatures using a temporary keyset. Note that the sample creates the signature with RSACryptoServiceProvider, and verifies it with CryptVerifySignature API after selecting the temporary keyset.
Form1.cs:
using System;
using System.Xml;
using System.Drawing;
using System.Collections;
using System.ComponentModel;
using System.Windows.Forms;
using System.Data;
using System.Security.Cryptography;
using System.Runtime.InteropServices;
using System.IO;
namespace WindowsApplication1
{
/// <summary>
/// Summary description for Form1.
/// </summary>
public class Form1 : System.Windows.Forms.Form
{
private System.Windows.Forms.GroupBox groupBox1;
private System.Windows.Forms.GroupBox groupBox2;
private System.Windows.Forms.Button button11;
private System.Windows.Forms.Button button21;
private System.Windows.Forms.TextBox textBox11;
private System.Windows.Forms.Label label11;
private System.Windows.Forms.TextBox textBox21;
private System.Windows.Forms.Label label31;
private System.Windows.Forms.TextBox textBox31;
private System.Windows.Forms.Label label21;
private System.Windows.Forms.Button button22;
private System.Windows.Forms.TextBox textBox12;
private System.Windows.Forms.Label label12;
private System.Windows.Forms.TextBox textBox22;
private System.Windows.Forms.Label label32;
private System.Windows.Forms.TextBox textBox32;
private System.Windows.Forms.Label label22;
private System.Windows.Forms.Button button12;
/// <summary>
/// Required designer variable.
/// </summary>
private System.ComponentModel.Container components = null;
public Form1()
{
//
// Required for Windows Form Designer support
//
InitializeComponent();
//
// TODO: Add any constructor code after InitializeComponent call
//
}
/// <summary>
/// Clean up any resources being used.
/// </summary>
protected override void Dispose( bool disposing )
{
if( disposing )
{
if (components != null)
{
components.Dispose();
}
}
base.Dispose( disposing );
}
#region Windows Form Designer generated code
/// <summary>
/// Required method for Designer support - do not modify
/// the contents of this method with the code editor.
/// </summary>
private void InitializeComponent()
{
this.button11 = new System.Windows.Forms.Button();
this.button21 = new System.Windows.Forms.Button();
this.textBox11 = new System.Windows.Forms.TextBox();
this.label11 = new System.Windows.Forms.Label();
this.textBox21 = new System.Windows.Forms.TextBox();
this.label31 = new System.Windows.Forms.Label();
this.textBox31 = new System.Windows.Forms.TextBox();
this.label21 = new System.Windows.Forms.Label();
this.groupBox1 = new System.Windows.Forms.GroupBox();
this.groupBox2 = new System.Windows.Forms.GroupBox();
this.button22 = new System.Windows.Forms.Button();
this.textBox12 = new System.Windows.Forms.TextBox();
this.label12 = new System.Windows.Forms.Label();
this.textBox22 = new System.Windows.Forms.TextBox();
this.label32 = new System.Windows.Forms.Label();
this.textBox32 = new System.Windows.Forms.TextBox();
this.label22 = new System.Windows.Forms.Label();
this.button12 = new System.Windows.Forms.Button();
this.groupBox1.SuspendLayout();
this.groupBox2.SuspendLayout();
this.SuspendLayout();
//
// button11
//
this.button11.Location = new System.Drawing.Point(8, 64);
this.button11.Name = "button11";
this.button11.Size = new System.Drawing.Size(216, 32);
this.button11.TabIndex = 0;
this.button11.Text = "Sign";
this.button11.Click += new System.EventHandler(this.button11_Click);
//
// button21
//
this.button21.Location = new System.Drawing.Point(8, 360);
this.button21.Name = "button21";
this.button21.Size = new System.Drawing.Size(216, 32);
this.button21.TabIndex = 1;
this.button21.Text = "Verify";
this.button21.Click += new System.EventHandler(this.button21_Click);
//
// textBox11
//
this.textBox11.Location = new System.Drawing.Point(8, 40);
this.textBox11.Name = "textBox11";
this.textBox11.Size = new System.Drawing.Size(216, 20);
this.textBox11.TabIndex = 2;
this.textBox11.Text = "OwT4Zk1hjsnSDOBdGSlkxdWGgoc=";
//
// label11
//
this.label11.Location = new System.Drawing.Point(8, 24);
this.label11.Name = "label11";
this.label11.Size = new System.Drawing.Size(104, 16);
this.label11.TabIndex = 3;
this.label11.Text = "Hash (Base64)";
//
// textBox21
//
this.textBox21.Location = new System.Drawing.Point(8, 120);
this.textBox21.Multiline = true;
this.textBox21.Name = "textBox21";
this.textBox21.ScrollBars = System.Windows.Forms.ScrollBars.Vertical;
this.textBox21.Size = new System.Drawing.Size(216, 104);
this.textBox21.TabIndex = 4;
this.textBox21.Text = "";
//
// label31
//
this.label31.Location = new System.Drawing.Point(8, 232);
this.label31.Name = "label31";
this.label31.Size = new System.Drawing.Size(168, 16);
this.label31.TabIndex = 5;
this.label31.Text = "Signature (Base64)";
//
// textBox31
//
this.textBox31.Location = new System.Drawing.Point(8, 248);
this.textBox31.Multiline = true;
this.textBox31.Name = "textBox31";
this.textBox31.ScrollBars = System.Windows.Forms.ScrollBars.Vertical;
this.textBox31.Size = new System.Drawing.Size(216, 104);
this.textBox31.TabIndex = 6;
this.textBox31.Text = "";
//
// label21
//
this.label21.Location = new System.Drawing.Point(8, 104);
this.label21.Name = "label21";
this.label21.Size = new System.Drawing.Size(104, 16);
this.label21.TabIndex = 7;
this.label21.Text = "RSA object (XML)";
//
// groupBox1
//
this.groupBox1.Controls.Add(this.button21);
this.groupBox1.Controls.Add(this.textBox11);
this.groupBox1.Controls.Add(this.label11);
this.groupBox1.Controls.Add(this.textBox21);
this.groupBox1.Controls.Add(this.label31);
this.groupBox1.Controls.Add(this.textBox31);
this.groupBox1.Controls.Add(this.label21);
this.groupBox1.Controls.Add(this.button11);
this.groupBox1.Location = new System.Drawing.Point(8, 8);
this.groupBox1.Name = "groupBox1";
this.groupBox1.Size = new System.Drawing.Size(232, 400);
this.groupBox1.TabIndex = 8;
this.groupBox1.TabStop = false;
this.groupBox1.Text = "No P/Invoke";
//
// groupBox2
//
this.groupBox2.Controls.Add(this.button22);
this.groupBox2.Controls.Add(this.textBox12);
this.groupBox2.Controls.Add(this.label12);
this.groupBox2.Controls.Add(this.textBox22);
this.groupBox2.Controls.Add(this.label32);
this.groupBox2.Controls.Add(this.textBox32);
this.groupBox2.Controls.Add(this.label22);
this.groupBox2.Controls.Add(this.button12);
this.groupBox2.Location = new System.Drawing.Point(248, 8);
this.groupBox2.Name = "groupBox2";
this.groupBox2.Size = new System.Drawing.Size(232, 400);
this.groupBox2.TabIndex = 9;
this.groupBox2.TabStop = false;
this.groupBox2.Text = "P/Invoke";
//
// button22
//
this.button22.Location = new System.Drawing.Point(8, 360);
this.button22.Name = "button22";
this.button22.Size = new System.Drawing.Size(216, 32);
this.button22.TabIndex = 1;
this.button22.Text = "Verify";
this.button22.Click += new System.EventHandler(this.button22_Click);
//
// textBox12
//
this.textBox12.Location = new System.Drawing.Point(8, 40);
this.textBox12.Name = "textBox12";
this.textBox12.Size = new System.Drawing.Size(216, 20);
this.textBox12.TabIndex = 2;
this.textBox12.Text = "OwT4Zk1hjsnSDOBdGSlkxdWGgoc=";
//
// label12
//
this.label12.Location = new System.Drawing.Point(8, 24);
this.label12.Name = "label12";
this.label12.Size = new System.Drawing.Size(104, 16);
this.label12.TabIndex = 3;
this.label12.Text = "Hash (Base64)";
//
// textBox22
//
this.textBox22.Location = new System.Drawing.Point(8, 120);
this.textBox22.Multiline = true;
this.textBox22.Name = "textBox22";
this.textBox22.ScrollBars = System.Windows.Forms.ScrollBars.Vertical;
this.textBox22.Size = new System.Drawing.Size(216, 104);
this.textBox22.TabIndex = 4;
this.textBox22.Text = "";
//
// label32
//
this.label32.Location = new System.Drawing.Point(8, 232);
this.label32.Name = "label32";
this.label32.Size = new System.Drawing.Size(168, 16);
this.label32.TabIndex = 5;
this.label32.Text = "Signature (Base64)";
//
// textBox32
//
this.textBox32.Location = new System.Drawing.Point(8, 248);
this.textBox32.Multiline = true;
this.textBox32.Name = "textBox32";
this.textBox32.ScrollBars = System.Windows.Forms.ScrollBars.Vertical;
this.textBox32.Size = new System.Drawing.Size(216, 104);
this.textBox32.TabIndex = 6;
this.textBox32.Text = "";
//
// label22
//
this.label22.Location = new System.Drawing.Point(8, 104);
this.label22.Name = "label22";
this.label22.Size = new System.Drawing.Size(104, 16);
this.label22.TabIndex = 7;
this.label22.Text = "RSA object (XML)";
//
// button12
//
this.button12.Location = new System.Drawing.Point(8, 64);
this.button12.Name = "button12";
this.button12.Size = new System.Drawing.Size(216, 32);
this.button12.TabIndex = 0;
this.button12.Text = "Sign";
this.button12.Click += new System.EventHandler(this.button12_Click);
//
// Form1
//
this.AutoScaleBaseSize = new System.Drawing.Size(5, 13);
this.ClientSize = new System.Drawing.Size(488, 414);
this.Controls.Add(this.groupBox1);
this.Controls.Add(this.groupBox2);
this.Name = "Form1";
this.Text = "Form1";
this.groupBox1.ResumeLayout(false);
this.groupBox2.ResumeLayout(false);
this.ResumeLayout(false);
}
#endregion
/// <summary>
/// The main entry point for the application.
/// </summary>
[STAThread]
static void Main()
{
Application.Run(new Form1());
}
private void button11_Click(object sender, System.EventArgs e)
{
textBox21.Text = "";
textBox31.Text = "";
// Create RSA object and show it
RSACryptoServiceProvider RSA = new RSACryptoServiceProvider();
textBox21.Text = RSA.ToXmlString(false);
// Sign hash
RSAPKCS1SignatureFormatter RSAFormatter = new RSAPKCS1SignatureFormatter(RSA);
RSAFormatter.SetHashAlgorithm("SHA1");
byte[] signedHashValue = RSAFormatter.CreateSignature(
Convert.FromBase64String(textBox11.Text));
// Show signature
textBox31.Text = Convert.ToBase64String(signedHashValue);
}
private void button21_Click(object sender, System.EventArgs e)
{
// Re-create RSA object
RSACryptoServiceProvider RSA = new RSACryptoServiceProvider();
RSA.FromXmlString(textBox21.Text);
// Verify signature
RSAPKCS1SignatureDeformatter RSADeformatter = new RSAPKCS1SignatureDeformatter(RSA);
RSADeformatter.SetHashAlgorithm("SHA1");
if (RSADeformatter.VerifySignature(
Convert.FromBase64String(textBox11.Text),
Convert.FromBase64String(textBox31.Text))
)
{
MessageBox.Show("Signature verified!");
}
else
{
MessageBox.Show("Signature NOT verified!");
}
}
private void button12_Click(object sender, System.EventArgs e)
{
textBox22.Text = "";
textBox32.Text = "";
// Create RSA object and show it
RSACryptoServiceProvider RSA = new RSACryptoServiceProvider();
textBox22.Text = RSA.ToXmlString(false);
// Sign hash
RSAPKCS1SignatureFormatter RSAFormatter = new RSAPKCS1SignatureFormatter(RSA);
RSAFormatter.SetHashAlgorithm("SHA1");
byte[] signedHashValue = RSAFormatter.CreateSignature(
Convert.FromBase64String(textBox12.Text));
// Show signature
textBox32.Text = Convert.ToBase64String(signedHashValue);
}
private void button22_Click(object sender, System.EventArgs e)
{
// Variables
IntPtr hProv = IntPtr.Zero;
String pszContainer = null;
String pszProvider = null;
UInt32 dwProvType = 0;
UInt32 dwFlags = 0;
IntPtr hHash = IntPtr.Zero;
Byte[] pbHash = null;
IntPtr hPubKey = IntPtr.Zero;
Byte[] pbSignature = null;
Byte[] pbModulus = null;
Byte[] pbExponent = null;
Byte[] pbPubKey = null;
try
{
// Acquire CSP
hProv = IntPtr.Zero;
pszContainer = null; // Required for crypt_verifycontext
pszProvider = null; // Can use null for default provider
dwProvType = Win32.PROV_RSA_FULL;
dwFlags = Win32.CRYPT_VERIFYCONTEXT; //No private key access required.
if (!Win32.CryptAcquireContext(ref hProv, pszContainer, pszProvider, dwProvType, dwFlags))
{
throw new Exception("CryptAcquireContext error", new Win32Exception(Marshal.GetLastWin32Error()));
}
// Create hash object
hHash = IntPtr.Zero;
if (!Win32.CryptCreateHash(hProv, Win32.CALG_SHA1, IntPtr.Zero, 0, ref hHash))
{
throw new Exception("CryptCreateHash error", new Win32Exception(Marshal.GetLastWin32Error()));
}
// Fill hash object with our hash
pbHash = Convert.FromBase64String(textBox12.Text);
if (!Win32.CryptSetHashParam(hHash, Win32.HP_HASHVAL, pbHash, 0))
{
throw new Exception("CryptSetHashParam error", new Win32Exception(Marshal.GetLastWin32Error()));
}
// Import public key
//
// Public Key BLOB:
// "
// PUBLICKEYSTRUC blobheader;
// RSAPUBKEY rsapubkey;
// BYTE modulus[rsapubkey.bitlen/8];
// "
XmlDocument xml = new XmlDocument();
xml.InnerXml = textBox22.Text;
pbModulus = Convert.FromBase64String(xml.FirstChild.ChildNodes.Item(0).InnerText);
pbExponent = Convert.FromBase64String(xml.FirstChild.ChildNodes.Item(1).InnerText);
MemoryStream keyBlob = new MemoryStream(
Marshal.SizeOf(typeof(Win32.PUBLICKEYSTRUC)) +
Marshal.SizeOf(typeof(Win32.RSAPUBKEY)) +
pbModulus.Length
);
BinaryWriter bw = new BinaryWriter(keyBlob);
bw.Write((Byte)Win32.PUBLICKEYBLOB); // blobheader.bType
bw.Write((Byte)Win32.CUR_BLOB_VERSION); // blobheader.bVersion
bw.Write((UInt16)0); // blobheader.reserved
bw.Write((UInt32)Win32.CALG_RSA_SIGN); // blobheader.aiKeyAlg
bw.Write((UInt32)0x31415352); // rsapubkey.magic = "RSA1"
bw.Write((UInt32)(pbModulus.Length * 8)); // rsapubkey.bitlen
// rsapubkey.pubexp
Byte[] buffer = new Byte[Marshal.SizeOf(typeof(UInt32))];
Array.Copy(pbExponent, 0, buffer, 0, pbExponent.Length);
bw.Write(buffer);
// modulus
Array.Reverse(pbModulus);
bw.Write(pbModulus);
pbPubKey = keyBlob.ToArray();
if (!Win32.CryptImportKey(hProv, pbPubKey, pbPubKey.Length, IntPtr.Zero, 0, ref hPubKey))
{
throw new Exception("CryptImportKey error", new Win32Exception(Marshal.GetLastWin32Error()));
}
// Verify signature
pbSignature = Convert.FromBase64String(textBox32.Text);
Array.Reverse(pbSignature);
if (!Win32.CryptVerifySignature(hHash, pbSignature, pbSignature.Length, hPubKey, null, 0))
{
throw new Exception("CryptVerifySignature error", new Win32Exception(Marshal.GetLastWin32Error()));
}
else
{
MessageBox.Show("Signature verified!!!");
}
}
catch (Exception ex)
{
// Any errors?
string msg;
if (ex.InnerException == null)
{
msg = ex.Message;
}
else
{
msg = ex.Message + " --> " + ex.InnerException.Message;
}
MessageBox.Show(msg);
}
finally
{
// Destroy hash
if (hHash != IntPtr.Zero)
{
Win32.CryptDestroyHash(hHash);
}
// Destroy public key
if (hPubKey != IntPtr.Zero)
{
Win32.CryptDestroyKey(hPubKey);
}
// Release the CSP
//
if (hProv != IntPtr.Zero)
{
Win32.CryptReleaseContext(hProv, 0);
}
}
}
}
}
Win32.cs:
using System;
using System.Windows.Forms;
using System.Runtime.InteropServices;
public class Win32
{
#region CONSTS
public const UInt32 CALG_SHA1 = (4 << 13) | 4;
public const UInt32 CALG_RSA_SIGN = (1 << 13) | (2 << 9);
public const UInt32 PROV_RSA_FULL = 0x00000001;
public const UInt32 CRYPT_VERIFYCONTEXT = 0xF0000000; //No private key access required
public const UInt32 X509_ASN_ENCODING = 0x00000001;
public const UInt32 PKCS_7_ASN_ENCODING = 0x00010000;
public const UInt32 HP_HASHVAL = 0x00000002;
public const UInt32 HP_HASHSIZE = 0x00000004;
public const UInt32 PUBLICKEYBLOBEX = 0x0A;
public const UInt32 PUBLICKEYBLOB = 0x06;
public const UInt32 CUR_BLOB_VERSION = 0x02;
public const UInt32 CRYPT_EXPORTABLE = 0x00000001;
#endregion
#region STRUCTS
[StructLayout(LayoutKind.Sequential)]
public struct PUBLICKEYSTRUC
{
public Byte bType;
public Byte bVersion;
public UInt16 reserved;
public UInt32 aiKeyAlg;
}
[StructLayout(LayoutKind.Sequential)]
public struct RSAPUBKEY
{
public UInt32 magic;
public UInt32 bitlen;
public UInt32 pubexp;
}
#endregion
#region FUNCTIONS
[DllImport("advapi32.dll", CharSet=CharSet.Auto, SetLastError=true)]
public static extern bool CryptAcquireContext(
ref IntPtr hProv,
String pszContainer,
String pszProvider,
UInt32 dwProvType,
UInt32 dwFlags
);
[DllImport("advapi32.dll", SetLastError=true)]
public static extern bool CryptCreateHash(
IntPtr hProv,
UInt32 Algid,
IntPtr hKey,
UInt32 dwFlags,
ref IntPtr phHash
);
[DllImport("advapi32.dll", SetLastError=true)]
public static extern bool CryptGetHashParam(
IntPtr hHash,
UInt32 dwParam,
ref UInt32 pbData,
ref UInt32 pdwDataLen,
UInt32 dwFlags
);
[DllImport("advapi32.dll", SetLastError=true)]
public static extern bool CryptSetHashParam(
IntPtr hHash,
UInt32 dwParam,
Byte[] pbData,
UInt32 dwFlags
);
[DllImport("crypt32.dll", CharSet=CharSet.Auto, SetLastError=true)]
public static extern bool CryptImportPublicKeyInfo(
IntPtr hCryptProv,
UInt32 dwCertEncodingType,
IntPtr pInfo,
ref IntPtr phKey
);
[DllImport("advapi32.dll", SetLastError=true)]
public static extern bool CryptImportKey(
IntPtr hProv,
Byte[] pbData,
Int32 dwDataLen,
IntPtr hPubKey,
UInt32 dwFlags,
ref IntPtr phKey
);
[DllImport("advapi32.dll", CharSet=CharSet.Auto, SetLastError=true)]
public static extern bool CryptVerifySignature(
IntPtr hHash,
Byte[] pbSignature,
Int32 dwSigLen,
IntPtr hPubKey,
String sDescription,
UInt32 dwFlags
);
[DllImport("advapi32.dll", SetLastError=true)]
public static extern bool CryptDestroyKey(
IntPtr hKey
);
[DllImport("advapi32.dll", SetLastError=true)]
public static extern bool CryptDestroyHash(
IntPtr hHash
);
[DllImport("advapi32.dll", SetLastError=true)]
public static extern bool CryptReleaseContext(
IntPtr hProv,
UInt32 dwFlags
);
[DllImport("advapi32.dll", SetLastError=true)]
public static extern bool CryptGenKey(
IntPtr hProv,
UInt32 Algid,
UInt32 dwFlags,
ref IntPtr phKey
);
[DllImport("advapi32.dll", SetLastError=true)]
public static extern bool CryptExportKey(
IntPtr hKey,
IntPtr hExpKey,
UInt32 dwBlobType,
UInt32 dwFlags,
Byte[] pbData,
ref UInt32 pdwDataLen
);
// Helper function to convert struts & classes to byte array
public static byte[] RawSerialize(object anything)
{
int rawsize = Marshal.SizeOf(anything);
IntPtr buffer = Marshal.AllocHGlobal(rawsize);
Marshal.StructureToPtr(anything, buffer, false);
byte[] rawdatas = new byte[rawsize];
Marshal.Copy(buffer, rawdatas, 0, rawsize);
Marshal.FreeHGlobal(buffer);
return rawdatas;
}
#endregion
}
I hope this helps.
Regards,
Alex (Alejandro Campos Magencio)