0

I'm working on a Powershell script, that needs to call a web service, with a Client and Service Certificate. I have the connect semi-working in C# .Net. In .Net app.config i have these configurations:

...
<security mode="TransportWithMessageCredential">
            <message clientCredentialType="Certificate" negotiateServiceCredential="false" establishSecurityContext="false"/>
            <transport clientCredentialType="Certificate"/>
          </security>
        </binding>
      </wsHttpBinding>
    </bindings>
    <behaviors>
      <endpointBehaviors>
        <behavior name="CertificateAuthenticationBehavior">
          <clientCredentials>
            <clientCertificate findValue="Capital Market FIONAsi" storeLocation="LocalMachine" storeName="My" x509FindType="FindBySubjectName" />
            <serviceCertificate>
              <defaultCertificate findValue="example.dk" storeLocation="LocalMachine" storeName="My" x509FindType="FindBySubjectName"/>
              <authentication certificateValidationMode="PeerTrust" />
            </serviceCertificate>
          </clientCredentials>
        </behavior>

The .net code.

var stinaProxy = new StinaServiceProxy("StinaService");
var stinaHandshakeResponse = stinaProxy.HandShake(testValueArgument);

This seems to get me passed certificate validation in .net

But as mentioned, I actually need this to work for me in powershell. I don't know how to call a webservice and suppy both the client and the service certificate.

Here is what I got so far in powershell, but it ends in timeout, witch I believe is a certificate problem.

    [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12

    $ClientCertificate = Get-ChildItem Cert:\LocalMachine\My\af4269b1d7190be23f1e48001fc345011f7ade80
    $defaultCertificate = Get-ChildItem Cert:\LocalMachine\My\42097f29a5bd2fb4d9960e74f67654d369b7a2e3
    $url = "https://example.dk/StinaService.svc?wsdl"
    $webserviceex = New-WebServiceProxy -Uri $url -Namespace WebServiceProxy 
    $webserviceex.Timeout = 5000
    $webserviceex.ClientCertificates.Add($ClientCertificate)
    $webserviceex.ClientCertificates.Add($defaultCertificate)
    $handshakeResult = $webserviceex.HandShake("1234!")

Any help is appreciated :)

Simon Bruun
  • 59
  • 1
  • 5
  • What do you mean by service certificate? It looks like you're doing the client certificate authentication correctly, but I'm not sure what `$defaultCertificate` is supposed to be used for. Do you have any access to the webservice to see why it doesn't respond? Normally a bad/missing certificate should return a 403. – Cpt.Whale Apr 13 '21 at 14:41
  • I guess your client is checking the Service Certificate for validity and failing or timing out at revocation list checks. I don't think you need to add the `$defaultcertificate` to the `ClientCertficates` collection, you just need to trust the Service certificate. Have you tried adding the `PeerTrust` attribute to your `$webservicex` [proxy](https://learn.microsoft.com/en-us/dotnet/api/system.servicemodel.security.x509servicecertificateauthentication.certificatevalidationmode?view=dotnet-plat-ext-5.0)? – Rich Moss Apr 13 '21 at 16:57
  • Hi guys, thanks for your inputs. – Simon Bruun Apr 14 '21 at 06:31
  • Hi guys, thanks for your inputs. Cpt.Whale, I think I need two certificates, but I'm not good at this certificate stuff. I don't have access to the webservice backend / logs. RichMoss, Actually I didn't believe that adding it to the ClientCertificates collection was the trick, just an attempt from my side. I have added the certificate to the machines store. I don't see the possibility to add the PeerTrust anywere in powershell. Do you know how to do that ? – Simon Bruun Apr 14 '21 at 06:37

1 Answers1

1

I doubt the ServiceCertificate is going to be the cause of your issues since only the client should care. Have you tried setting your $webserviceex with only the $ClientCertificate added? Usually, you would only add additional certificates if you had a chain and needed CA certificates too.


I did find how to add service certificates in powershell. You didn't include the classes you used, but I was able to use [System.ServiceModel.ServiceHost] to create something similar to what you have. here is a powershell example of a webservice using client and server certificates.

[URI]$URI = 'https://example.dk/StinaService.svc'

# WSHttpBinding
$binding = New-Object System.ServiceModel.WSHttpBinding
$binding.Security.Mode = [System.ServiceModel.SecurityMode]::TransportWithMessageCredential
$binding.Security.Transport.ClientCredentialType = [System.ServiceModel.HttpClientCredentialType]::Certificate
$binding.Security.Message.ClientCredentialType   = [System.ServiceModel.MessageCredentialType]::Certificate

# Create service host
$ServiceHost = [System.ServiceModel.ServiceHost]::new([StinaService], $URI)
    $ServiceHost.AddServiceEndpoint([IStinaService], $binding,"")

    # Add Service Certificate (authenticate the server)
    $ServiceHost.Credentials.ServiceCertificate.SetCertificate(
        [System.Security.Cryptography.X509Certificates.StoreLocation]::LocalMachine,
        [System.Security.Cryptography.X509Certificates.StoreName]::My,
        [System.Security.Cryptography.X509Certificates.X509FindType]::FindBySubjectName,
        'example.dk'
    )
    # Add Client Certificate (authenticate the client)
    $ServiceHost.Credentials.ClientCertificate.SetCertificate(
        [System.Security.Cryptography.X509Certificates.StoreLocation]::LocalMachine,
        [System.Security.Cryptography.X509Certificates.StoreName]::My,
        [System.Security.Cryptography.X509Certificates.X509FindType]::FindBySubjectName,
        'Capital Market FIONAsi' 
    )

# Open the service
$ServiceHost.Open()

# Close the service.
$ServiceHost.Close()

I based this off of the example .net WCF code in this MS how-to, and defined the [StinaService] ServiceContract using the definitions from Chrissy Lemaire in her powershell tcp service proof-of-concept.


Note that you can probably ignore this example - just copy your existing .Net code, format it to powershell like this, and import the same app.config you are already using.

[System.AppDomain]::CurrentDomain.SetData("APP_CONFIG_FILE", "$dllPath.config")
$null = [Reflection.Assembly]::LoadFrom($dllPath)

Copied from https://stackoverflow.com/a/33927024/7411885.

Cpt.Whale
  • 4,784
  • 1
  • 10
  • 16
  • Thank you Cpt. Whale It seems like the right approach on your powershell example. I had to add this line first: Add-Type -AssemblyName System.ServiceModel But the script then fails to run this line: $ServiceHost = [System.ServiceModel.ServiceHost]::new([StinaService], $URI) Unable to find type [StinaService]. – Simon Bruun Apr 15 '21 at 08:14
  • @SimonBruun Sorry, I'm not very familiar with the actual service definition and binding steps. `AddServiceEndpoint()` might not be what you need if you're using a wsdl. – Cpt.Whale Apr 16 '21 at 02:49