Wednesday, September 7, 2011

WCF Service Call with X.509 Certificate Using Powershell

I ran into a situation where I needed to be able to query a WCF service that used an X.509 certificate. I searched the web and was left wanting. So, I turned to powershell. I had previously used powershell to call web services (mostly in Powershell v1), but never hit a WCF service. I found a useful post that took me a little farther, namely it got me to the point where I could script the proxy code generation and compiling the assembly.

I added code to make the binding use the certificate (highlighted line 9 below) and then specified the certificate to use in the client proxy object. My certificate is installed into the Personal store on the local machine (highlighted line 13 below).

Upon testing the code (sans line 10). I received an error "If this is a legitimate remote endpoint, you can fix the problem by explicitly specifying DNS identity '' as the Identity property of EndpointAddress when creating channel proxy." It was trying to match the partial domain name to the fully distinguished domain name. I found that I could fix this limitation by specifying the DNS entry that I was expecting on the endpoint (highlighted line 10).

$proxy = "http://ws.logos.domain:8181/LogosService.svc";
& 'C:\Program Files\Microsoft SDKs\Windows\v6.0A\Bin\SvcUtil.exe' "$proxy?wsdl"
& 'C:\Windows\Microsoft.NET\Framework\v3.5\csc.exe'  /t:library LogosService.cs /r:"C:\Program Files\Reference Assemblies\Microsoft\Framework\v3.0\System.ServiceModel.dll"

[Reflection.Assembly]::LoadWithPartialName("System.ServiceModel");
[Reflection.Assembly]::LoadFrom("$pwd\LogosService.dll");

$wsHttpBinding = New-Object System.ServiceModel.WSHttpBinding;
$wsHttpBinding.Security.Message.ClientCredentialType = [System.ServiceModel.MessageCredentialType]::Certificate;
$endpoint = New-Object System.ServiceModel.EndpointAddress($memberProxy, [System.ServiceModel.EndpointIdentity]::CreateDNSIdentity("ws.Logos"))

$proxy = New-Object MemberServiceClient($wsHttpBinding, $endpoint)
$proxy.ClientCredentials.ClientCertificate.SetCertificate([System.Security.Cryptography.X509Certificates.StoreLocation]::LocalMachine, [System.Security.Cryptography.X509Certificates.StoreName]::My, [System.Security.Cryptography.X509Certificates.X509FindType]::FindBySubjectName, "ws.Logos");
$request = New-Object LogosService.ServiceContracts.GetUserInfoRequest;
$request.RequestId = [Guid]::NewGuid();
$request.login = 'domain\test';

$response = $proxy.GetUserInfo($request);

Once I had the $response object, I could get at all the information I needed. Pretty useful.

No comments: