Saturday, October 10, 2009

Accessing Private Key from PFX (PKCS#12) in .Net Framework

You may know that .Net Framework has X509Certificate2 class to read X.509 certificate and obtain the public key from the certificate:

var cert = new X509Certificate2("my.cer");
var key = cert.PublicKey.Key as RSACryptoServiceProvider;
var cipher = key.Encrypt(Encoding.UTF8.GetBytes(txtClear.Text), false);
txtCipher.Text = Convert.ToBase64String(cipher);

However, you may not know that X509Certificate2 class can also read the first private key from a PFX file without CryptoAPI:

var cert = new X509Certificate2("my.pfx", "password");
var key = cert.PrivateKey as RSACryptoServiceProvider;
var clear = Convert.FromBase64String(txtCipher.Text);
txtClear.Text = Encoding.UTF8.GetString(key.Decrypt(clear, false));

To read all private keys from PFX, use PKCS12.Read() method below. It uses CryptoAPI and it will return an array of X509Certificate2. You can get the private key to decrypt the cipher as shown above. The full sample code can be download at Shane's Shelf.

using System;
using System.IO;
using System.Runtime.InteropServices;
using System.Security.Cryptography.X509Certificates;
using System.Collections.Generic;

namespace X509Cert
{
public class PKCS12
{
public static X509Certificate2[] Read(string filename, string password)
{

FileStream stream = new FileStream(filename, FileMode.Open);
byte[] buffer = new byte[stream.Length];
stream.Read(buffer, 0, buffer.Length);
stream.Close();


WIN32.CRYPT_DATA_BLOB cryptdata = new WIN32.CRYPT_DATA_BLOB();
cryptdata.cbData = buffer.Length;
cryptdata.pbData = Marshal.AllocHGlobal(cryptdata.cbData);
Marshal.Copy(buffer, 0, cryptdata.pbData, buffer.Length);
IntPtr hMemStore = WIN32.PFXImportCertStore(ref cryptdata, password, WIN32.CRYPT_USER_KEYSET);
Marshal.FreeHGlobal(cryptdata.pbData);

uint provinfosize = 0;

List<X509Certificate2> certs = new List<X509Certificate2>();

IntPtr certHandle = IntPtr.Zero;
while ((certHandle = WIN32.CertEnumCertificatesInStore(hMemStore, certHandle)) != IntPtr.Zero)
{

if (WIN32.CertGetCertificateContextProperty(certHandle, WIN32.CERT_KEY_PROV_INFO_PROP_ID, IntPtr.Zero, ref provinfosize))
{

IntPtr info = Marshal.AllocHGlobal((int)provinfosize);

if (WIN32.CertGetCertificateContextProperty(certHandle, WIN32.CERT_KEY_PROV_INFO_PROP_ID, info, ref provinfosize))
{
var certData = new X509Certificate2(certHandle).Export(X509ContentType.SerializedCert);
certs.Add(new X509Certificate2(certData));
}
Marshal.FreeHGlobal(info);

}
}

Marshal.FreeHGlobal(hMemStore);
return certs.ToArray();

}
}

public class WIN32
{
public const uint CRYPT_USER_KEYSET = 0x00001000;
public const uint CERT_KEY_PROV_INFO_PROP_ID = 0x00000002;

[DllImport("crypt32.dll", SetLastError = true)]
public static extern IntPtr PFXImportCertStore(ref CRYPT_DATA_BLOB pPfx, [MarshalAs(UnmanagedType.LPWStr)] String szPassword, uint dwFlags);

[DllImport("CRYPT32.DLL", EntryPoint = "CertEnumCertificatesInStore", CharSet = CharSet.Auto, SetLastError = true)]
public static extern IntPtr CertEnumCertificatesInStore(IntPtr storeProvider, IntPtr prevCertContext);

[DllImport("CRYPT32.DLL", CharSet = CharSet.Auto, SetLastError = true)]
public static extern bool CertGetCertificateContextProperty(IntPtr pCertContext, uint dwPropId, IntPtr pvData, ref uint pcbData);

[DllImport("advapi32.dll", EntryPoint = "CryptAcquireContext", CharSet = CharSet.Auto, SetLastError = true)]
public static extern bool CryptAcquireContext(ref IntPtr phProv, string szContainer, string szProvider, uint dwProvType, uint dwFlags);

[StructLayout(LayoutKind.Sequential)]
public struct CRYPT_DATA_BLOB
{
public int cbData;
public IntPtr pbData;
}

public WIN32()
{

}
}
}

The above code is altered base on http://www.cnblogs.com/rainlake/archive/2005/09/15/237997.html

Thursday, May 21, 2009

MXHR

Digg is creating a multipart capable XHR library. Good try but not very useful. Here is why:
  1. MXHR response is not compressed by default.
    This is illustrated very well in their text only demo. MXHR stream is always slower than normal mode. I opened up the packet sniffer. The normal stream is compressed while the MXHR is not.
  2. Data URI scheme is broken.
    The web is broken by IE for a long time. IE6 and IE7 will be still around for a few years. Image data cannot be served in cross-browser manner. The same performance can be achieved by spriting: putting the tiny-images into one image (overhead is small if the image is large enough), preload the combined imaged, and use background-position to get the coordination.
  3. Multipart is not streaming
    Well, it is under DUI.Stream namespace. However, it is not streaming. The server is packing up the data into one response. The library is processing the data on-the-fly but the received data is accumulating.
    1. If the size of the response is not large (i.e. can be stream in a couple seconds), enveloping the message in JSON or XML could be easier.
    2. If the size of the response is large (i.e. comet style), the responseText is going to become very large. We call it memory leak.
HTTP is broken because it is not intended to serve for Web 2.0. That's why there are so many facilities in CSS and JavaScript to patch the protocol. MXHR is definitely not the answer.

Monday, May 04, 2009

E4X Alternative - JSOM

I have been expecting the popularity of E4X for a long time. However, as long as Internet Explorer is the most popular browser, "new" standards will never get popular (Microsoft is suffered from Windows XP legacy, too). Instead of waiting for E4X, some developers use XPath to query the DOM nodes while the other traverse the DOM node by node. However, it would be good if we could access XML content in JavaScript Object Model (JSOM):

xml='<invoice date="01-20-2000" number="123">' +
' <address country="US">' +
' <name>John Smith</name>' +
' <street>123 George St.</street>' +
' <city>Mountain View</city>' +
' <state>CA</state>' +
' <zip>94041</zip>' +
' </address>' +
'</invoice>';
invoice = JSOM(xml);
alert(invoice.$number);
alert(invoice.address.name);

There is a project, IEE4X, in SourceForge for the same purpose but it does not work quite well for a few cases. So, I grabbed the idea and implement the JSOM library myself. The code can be download at Shane's Shelf.

Wednesday, April 22, 2009

XML vs. JSON - Size Comparison

I was wondering how compact is JSON comparing to XML. I grabbed an arbitrary XML from the web and convert it to an equivalent JSON format. It turns out that JSON is around two-third of the XML size.

XML: 596 bytes

JSON: 402 bytes

<?xml version="1.0"?>
<message>
<header>
<to>companyReceiver</to>
<from>companySender</from>
<type>saveInvoice</type>
</header>
<info>
<saveInvoice>
<invoice date="01-20-2000" number="123">
<address country="US">
<name>John Smith</name>
<street>123 George St.</street>
<city>Mountain View</city>
<state>CA</state>
<zip>94041</zip>
</address>
<billTo country="US">
<name>Company A</name>
<street>100 Main St.</street>
<city>Washington</city>
<state>DC</state>
<zip>20015</zip>
</billTo>
<items>
<item number="1">
<name>IBM A20 Laptop</name>
<quantity>1</quantity>
<USPrice>2000.00</USPrice>
</item>
</items>
</invoice>
</saveInvoice>
</info>
</message>
 
{
header:{
to:"companyReceiver",
from:"companySender",
type:"saveInvoice”
},
info:{
saveInvoice:{
invoice:{date:"01-20-2000,number:"123",
address:{country:"US",
name:"John Smith",
street:"123 George St.",
city:"Mountain View",
state:"CA",
zip:"94041"
},
billTo:{country="US",
name:"Company A",
street:"100 Main St.",
city:"Washington",
state:"DC",
zip:"20015"
},
items:[
{
name:"IBM A20 Laptop",
quantity:1,
USPrice:2000.00
}
]
}
}
}
}

Saturday, March 14, 2009

Secure WCF Services with Authentication Service

We can use WCF Authentication Service to authenticate users with ASP.NET membership provider. However, other WCF services are not protected by the authentication service out-of-the-box. That is, WCF services is not using ASP.NET forms authentication.

Fortunately, it is not hard to enable it. The magic point is set the HttpContext.Current.User in Global.asax
public class Global : System.Web.HttpApplication
{
// other methods snipped...
protected void Application_AuthenticateRequest(object sender, EventArgs e)
{
HttpCookie ticketCookie = Context.Request.Cookies[FormsAuthentication.FormsCookieName];
if (null == ticketCookie)
{
return;
}

FormsAuthenticationTicket ticket = FormsAuthentication.Decrypt(ticketCookie.Value);
if (null != ticket)
{
HttpContext.Current.User = new GenericPrincipal(new FormsIdentity(ticket), null);
}
}
}
In the service you want to protect, set the requirement mode to allowed or required.
    [AspNetCompatibilityRequirements(RequirementsMode = AspNetCompatibilityRequirementsMode.Allowed)]
public class PrimeService : IPrimeService
Then, throw in the following checking at the beginning of the method.
            if (!HttpContext.Current.User.Identity.IsAuthenticated)
{
throw new FaultException<SecurityAccessDeniedException>(new SecurityAccessDeniedException());
}
That would be good enough for Silverlight client. For .Net WCF client, you need to handle the HTTP cookies by yourself (Authentication Service is using the authentication ticket in cookies). Detail discussion can be found in the article in Shane's Shelf.

Tuesday, March 03, 2009

Silverlight Multi-part File Upload Form Post

Silverlight not actually very web friendly. It lacks of built-in support for the current HTML form post protocol. So, we have to write it our own. I have written an extension class and two serializer classes, one for normal form post (DataContractQueryStringSerializer) while the other for multipart upload form post (DataContractMultiPartSerializer).

If you only want to have the working code, just copy the code below. Detail explanation is available at Multi-Part Form Post in Shane's Shelf


public static class Extensions
{
public static void PostFormAsync(this HttpWebRequest request, object parameters, AsyncCallback callback)
{
request.Method = "POST";
request.ContentType = "application/x-www-form-urlencoded";
request.BeginGetRequestStream(new AsyncCallback(asyncResult =>
{
Stream stream = request.EndGetRequestStream(asyncResult);
DataContractQueryStringSerializer ser = new DataContractQueryStringSerializer();
ser.WriteObject(stream, parameters);
stream.Close();
request.BeginGetResponse(callback, request);
}), request);
}

public static void PostMultiPartAsync(this HttpWebRequest request, object parameters, AsyncCallback callback)
{
request.Method = "POST";
string boundary = "---------------" + DateTime.Now.Ticks.ToString();
request.ContentType = "multipart/form-data; boundary=" + boundary;
request.BeginGetRequestStream(new AsyncCallback(asyncResult =>
{
Stream stream = request.EndGetRequestStream(asyncResult);

DataContractMultiPartSerializer ser = new DataContractMultiPartSerializer(boundary);
ser.WriteObject(stream, parameters);
stream.Close();
request.BeginGetResponse(callback, request);
}), request);
}
}

public class DataContractQueryStringSerializer
{
public void WriteObject(Stream stream, object data)
{
StreamWriter writer = new StreamWriter(stream);
if (data != null)
{
if (data is Dictionary<string, string>)
{
foreach (var entry in data as Dictionary<string, string>)
{
writer.Write("{0}={1}&", entry.Key, entry.Value);
}
}
else
{
foreach (var prop in data.GetType().GetFields())
{
foreach (var attribute in prop.GetCustomAttributes(true))
{
if (attribute is DataMemberAttribute)
{
DataMemberAttribute member = attribute as DataMemberAttribute;
writer.Write("{0}={1}&", member.Name ?? prop.Name, prop.GetValue(data));
}
}
}
foreach (var prop in data.GetType().GetProperties())
{
if (prop.CanRead)
{
foreach (var attribute in prop.GetCustomAttributes(true))
{
if (attribute is DataMemberAttribute)
{
DataMemberAttribute member = attribute as DataMemberAttribute;
writer.Write("{0}={1}&", member.Name ?? prop.Name, prop.GetValue(data, null));
}
}
}
}
}
writer.Flush();
}
}
}

public class DataContractMultiPartSerializer
{
private string boundary;
public DataContractMultiPartSerializer(string boundary)
{
this.boundary = boundary;
}

private void WriteEntry(StreamWriter writer, string key, object value)
{
if (value != null)
{
writer.Write("--");
writer.WriteLine(boundary);
if (value is FileInfo)
{

FileInfo f = value as FileInfo;
writer.WriteLine(@"Content-Disposition: form-data; name=""{0}""; filename=""{1}""", key, f.Name);
writer.WriteLine("Content-Type: application/octet-stream");
writer.WriteLine("Content-Length: " + f.Length);
writer.WriteLine();
writer.Flush();
Stream output = writer.BaseStream;
Stream input = f.OpenRead();
byte[] buffer = new byte[4096];
for (int size = input.Read(buffer, 0, buffer.Length); size > 0; size = input.Read(buffer, 0, buffer.Length))
{
output.Write(buffer, 0, size);
}
output.Flush();
writer.WriteLine();
}
else
{
writer.WriteLine(@"Content-Disposition: form-data; name=""{0}""", key);
writer.WriteLine();
writer.WriteLine(value.ToString());
}
}
}

public void WriteObject(Stream stream, object data)
{
StreamWriter writer = new StreamWriter(stream);
if (data != null)
{
if (data is Dictionary<string, object>)
{
foreach (var entry in data as Dictionary<string, object>)
{
WriteEntry(writer, entry.Key, entry.Value);
}
}
else
{
foreach (var prop in data.GetType().GetFields())
{
foreach (var attribute in prop.GetCustomAttributes(true))
{
if (attribute is DataMemberAttribute)
{
DataMemberAttribute member = attribute as DataMemberAttribute;
WriteEntry(writer, member.Name ?? prop.Name, prop.GetValue(data));
}
}
}
foreach (var prop in data.GetType().GetProperties())
{
if (prop.CanRead)
{
foreach (var attribute in prop.GetCustomAttributes(true))
{
if (attribute is DataMemberAttribute)
{
DataMemberAttribute member = attribute as DataMemberAttribute;
WriteEntry(writer, member.Name ?? prop.Name, prop.GetValue(data, null));
}
}
}
}
}
}
writer.Write("--");
writer.Write(boundary);
writer.WriteLine("--");
writer.Flush();
}
}

The usage is as follows:
First a PHP file

<?php
print_r($_REQUEST);
$src = $_FILES['y']['tmp_name'];
$dest = "C:\\Windows\\Temp\\".$_FILES['y']['name'];
echo $src;
echo "\r\n";
echo $dest;
echo @copy($src, $dest);
?>
Then the Page control
    public partial class Page : UserControl
{
public Page()
{
InitializeComponent();
// Create a request object
HttpWebRequest request = (HttpWebRequest)WebRequest.Create(new Uri("http://localhost/rms/test.php"));
OpenFileDialog dlg = new OpenFileDialog();
if (dlg.ShowDialog().Value)
{
request.PostMultiPartAsync(new Dictionary<string, object> { { "x", "1" }, { "y", dlg.File } }, new AsyncCallback(asyncResult =>
{
HttpWebResponse response = (HttpWebResponse)request.EndGetResponse(asyncResult);

Stream responseStream = response.GetResponseStream();
StreamReader reader = new StreamReader(responseStream);
this.Dispatcher.BeginInvoke(delegate
{
// output is a TextBlock
output.Text = reader.ReadToEnd();
response.Close();
});
}));
}
}
}
Since it is able to serialize data contract, you could actually replace
new Dictionary<string, object> { { "x", "1" }, { "y", dlg.File } }

with
new Point(){X=1, Y=2}

given the point class is like this:
    [DataContract]
public class Point
{
[DataMember]
public int X { get; set; }
[DataMember(Name="y")]
public int Y { get; set; }
}

Wednesday, December 17, 2008

Slow Performance of PHP in Windows IIS

There are a few reasons that PHP runs slowly in Windows. Not because of the request delegation from IIS to PHP via FastCGI or CGI. In fact, the delegation is quite robust in FastCGI.

TCP/IP connection problem between database and web server may be a reason. Some Windows specific behaviors may be another. One of the Windows specific behaviors is the invocation of gethostbyaddr() function.

Here is the high level behavior of gethostbyaddr() function making the invocation slow:
Original
gethostbyaddr() on windows waits for WINS resolution timeout even when you disable WINS on the server.

This means that your DNS server may return "No hostname found" and windows gethostbyaddr() will just sit there, never having asked a WINS server anything, for 3 - 4 seconds. I watched it do just that with a sniffer.
Here is the root cause:
Original
Difference between gethostbyaddr and getnameinfo.

Originally posted by: Gregor The Eye

First at all, sorry for my english. It's not my native language, and, unfortunately, i havn't time to verify all text i enter.

Now, to subject. Computer may have many "names", and two main name groups of names computers have is a "DNS" names and a "NetBIOS" names.

The "NetBIOS" name is aname you assign to your computer using system->network identification->network ID. This name
is displayed in you "network neighborhood" and generally used in local networks to identify computers.

The "DNS" name is a name given to machine in internet. This name is a synonim to machine IP (becouse IP is\s not huma-friendly). "DNS" name is stored in special internet services (so-called "DNS" servers), not at the computer itself. Generally, computer can be shut off, but it's DNS name will be available and produce correct IP.

For example, if I install OS to new computer and call it "EYE", it will have a NetBIOS name "EYE", and, connecting it to my local network, i can access it in "network neighborhood". But, from the internet, i can't access to it using "EYE" name, and instead i must use it's IP - for example - 113.54.25.14. But if i need over users to access it from internet using a human-friendly name, i pay amount of money to sertain organization, and they register a name for it - for example - "www.GregorTheEye.com". Then, user can access my machine using this name, fopr example, writing:

ping www.GregorTheEye.com

or

telnet www.GregorTheEye.com

So, we have 2 names for machine - NetBIOS name and DNS name.
In my local network, following commands will be valid:

ping 113.54.25.14
ping EYE
ping www.GregorTheEye.com

Third command will be valid ONLY if my computer is conected to internet.

And from the internet, valid commands are:

ping 113.54.14
ping www.GregorTheEye.com

As you can see, NetBIOS name is not available from internet.

The main difference between NetBIOS name and DNS name is that DNS name will be available only if computer is connected to the internet and has i's name registered on it. NetBIOS name will be always available to computers that directly connected to target one.

To get DNS name, you must send a request to DNS server (it's IP is written in the system registry if you computer is connected to internet). If DNS server is unavailable, it will take default timeout time to discover this. If it's available, DNS server will return you human-friendly name of target machine if it's exist in database.

To obtain a NetBIOS name, you must send UDP packe to target machine and wait for responce. Becouse UDP is not a guaranteed-to-deliver protocol, responce may ot came, came corrupted, came many times etc. Your program must patiently wait for responce (it's not from 137 port - you must SEND UDP packet to 137 port of target machine, but responce will be returned to the port you specify. Only Win95 no OSR2 has a bug there responce always returns to port 137).

And, finally to Win32 functions -

gethostbyaddr() will first try to connect DNS server, and, if it's unavailable, will try to get NetBIOS name. So, it will get machine name one method or another in most cases.

getnameinfo() will ONLY try a DNS name, no attempts to get a NetBIOS name(). That's the difference.

If you write a program that must get names of local machines as well as names of internet machines (for example, network or port scanner), you MUST use gethostbyaddr(). If you write a program that will operate only with names of remote machines (IRC client, for example) - you MUST use getnameinfo() becouse Microsoft instructs so in lates MSDN releases.

If you have any questions - feel free to contact me. Also, if i write something wrony, i will be very thankfull if you send ma a hint.

Best regards, Gregor The Eye.
However, there is no getnameinfo() function in PHP. One of the nice feature in PHP documentation is the commenting. A commenter contributed a gethostbyaddr_timeout() function that only query the DNS server. You need to pass the DNS address (instead of using the OS specified DNS) to the function, though.