Calling WCF over HTTPS in Service Fabric : The request was aborted: Could not create SSL/TLS secure channel

落花浮王杯 提交于 2020-07-09 11:58:13

问题


I'm calling a WCF service over HTTPS. => The certificates are ok. See screenshot:

The client certificates are installed under my account and local computer. Both available for export.

So I have a piece of code that works in a console application. When I run the console app under Network Service, the service call works.

When I paste this code inside a StatefullService (inside service fabric) I get the following exception.

I've verified the ServicePointManager.SecurityProtocol It's System.Net.SecurityProtocolType.Tls | System.Net.SecurityProtocolType.Tls11 | System.Net.SecurityProtocolType.Tls12 in the console application and in service fabcric.

"System.ServiceModel.Security.SecurityNegotiationException: Could not establish secure channel for SSL/TLS with authority '********.cloudapp.azure.com'. ---> System.Net.WebException: The request was aborted: Could not create SSL/TLS secure channel.\r\n at System.Net.HttpWebRequest.GetResponse()\r\n at System.ServiceModel.Channels.HttpChannelFactory1.HttpRequestChannel.HttpChannelRequest.WaitForReply(TimeSpan timeout)\r\n --- End of inner exception stack trace ---\r\n\r\nServer stack trace: \r\n at System.ServiceModel.Channels.HttpChannelUtilities.ProcessGetResponseWebException(WebException webException, HttpWebRequest request, HttpAbortReason abortReason)\r\n at System.ServiceModel.Channels.HttpChannelFactory1.HttpRequestChannel.HttpChannelRequest.WaitForReply(TimeSpan timeout)\r\n at System.ServiceModel.Channels.RequestChannel.Request(Message message, TimeSpan timeout)\r\n at System.ServiceModel.Dispatcher.RequestChannelBinder.Request(Message message, TimeSpan timeout)\r\n at System.ServiceModel.Channels.ServiceChannel.Call(String action, Boolean oneway, ProxyOperationRuntime operation, Object[] ins, Object[] outs, TimeSpan timeout)\r\n at System.ServiceModel.Channels.ServiceChannelProxy.InvokeService(IMethodCallMessage methodCall, ProxyOperationRuntime operation)\r\n at System.ServiceModel.Channels.ServiceChannelProxy.Invoke(IMessage message)\r\n\r\nException rethrown at [0]: \r\n at System.Runtime.Remoting.Proxies.RealProxy.HandleReturnMessage(IMessage reqMsg, IMessage retMsg)\r\n at System.Runtime.Remoting.Proxies.RealProxy.PrivateInvoke(MessageData& msgData, Int32 type)\r\n at **********************************\r\n at ********************************* in ***********************.cs:line 75\r\n at *********************** in ***********************.cs:line 34\r\n at *********************** in ***********************.cs:line 20"

The code is the following



            var binding = new BasicHttpBinding();
            binding.Security.Mode = BasicHttpSecurityMode.Transport;
            binding.Security.Transport = new HttpTransportSecurity
            {
                ClientCredentialType = HttpClientCredentialType.Certificate,
            };
            string endpoint = "https://********.cloudapp.azure.com/*****/SomeService.svc";
            var endpoint1 = new EndpointAddress(endpoint);

            var factory = new ChannelFactory(binding, endpoint);

            var clientCredentials = new ClientCredentials();
            clientCredentials.ClientCertificate.SetCertificate("CN=my-cn-name",
                System.Security.Cryptography.X509Certificates.StoreLocation.LocalMachine,
                System.Security.Cryptography.X509Certificates.StoreName.My);

            if (factory.Endpoint.EndpointBehaviors.Contains(typeof(ClientCredentials)))
            {
                factory.Endpoint.EndpointBehaviors.Remove(typeof(ClientCredentials));
            }

            factory.Endpoint.EndpointBehaviors.Add(clientCredentials);

            var channel = factory.CreateChannel();

            try
            {
                var result = channel.GetData(1);

                Console.WriteLine("Success");
                Console.ReadLine();
            }
            catch (Exception e)
            {
                Console.WriteLine(e);
                Console.ReadLine();
            }

What I'm I missing to call the WCF service over HTTPS in service fabric? If I disable the HTTPS protocol and enable the HTTP protocol on the existing WCF service i'm able to connect. But for obvious reasons we need HTTPS.

Edit 1

Tested:

  1. Called the service with HttpClient and WebRequestHandler as GET to receive the HTML this works also in the console application. Not in service fabric.
  2. Removed the certificate from my personal store. Console application keeps working because it uses the local machine store (see code sample above)
  3. Doing the soap request in postman over HTTPS works.

Edit 2

Following this answer https://serverfault.com/a/132791/135762 (for network service) made it work.


回答1:


The problem was that network service account did not have the correct access rights to access the private key of the certificate.

You can solve this in 2 ways.

First one use powershell and a setup.bat to install and configure the certificates.

In your service fabric service add Setup.bat in the root of the project. Edit the service manifest and add a setup entry point

<ExeHost> <Program>Setup.bat</Program> <WorkingFolder>CodePackage</WorkingFolder> </ExeHost>

The bat file should probably run with elevated trust. You can add the following in the Application Manifest Inside the ServiceManifestImport:

<Policies> <RunAsPolicy CodePackageRef="Code" UserRef="SetupAdminUser" EntryPointType="Setup" /> </Policies>

At the bottom of the application manifest (in the root of the xml) add the following to run as admin. <Principals> <Users> <User Name="SetupAdminUser"> <MemberOf> <SystemGroup Name="Administrators" /> </MemberOf> </User> </Users> </Principals>

The bat file is simple: powershell.exe -ExecutionPolicy Bypass -Command ".\Scripts\Install-Certificates.ps1"

The powershell script can look like this


$pwd = ConvertTo-SecureString -String "YourPassword" -Force -AsPlainText

function Set-CertificatePermission
{
 param
 (
    [Parameter(Position=1, Mandatory=$true)]
    $cert ,

    [Parameter(Position=2, Mandatory=$true)]
    [ValidateNotNullOrEmpty()]
    [string]$serviceAccount
 )


 # Specify the user, the permissions and the permission type
 $permission = "$($serviceAccount)","Read,FullControl","Allow"
 $accessRule = New-Object -TypeName System.Security.AccessControl.FileSystemAccessRule -ArgumentList $permission;

 # Location of the machine related keys
 $keyPath = $env:ProgramData + "\Microsoft\Crypto\RSA\MachineKeys\";
 $keyName = $cert.PrivateKey.CspKeyContainerInfo.UniqueKeyContainerName;
 $keyFullPath = $keyPath + $keyName;

 try
 {
    # Get the current acl of the private key
    $acl = (Get-Acl $keyFullPath)

    # Add the new ace to the acl of the private key
    $acl.AddAccessRule($accessRule);

    # Write back the new acl
    Set-Acl -Path $keyFullPath -AclObject $acl;
 }
 catch
 {
    throw $_;
 }
}

function Install-RootCA($path){
    Write-Host "Installing root certificate"

    Import-PfxCertificate -FilePath $path -CertStoreLocation "Cert:\LocalMachine\Root" -Password $pwd -Exportable
}

function Install-Certificate($path){
    Write-Host "Installing certificate"

     $cert = Import-PfxCertificate -FilePath $path -CertStoreLocation "Cert:\LocalMachine\My" -Password $pwd -Exportable
     Set-CertificatePermission $cert "NT AUTHORITY\NETWORK SERVICE"
}



Install-RootCA ".\Certificates\CARoot.pfx"
Install-Certificate ".\Certificates\ClientCert.pfx"

This will install a certificate in the trusted root store (because we use self signed certificates) and install a certificate in the Personal store of the computer account. The important bit for Service Fabric is it also sets correct rights on the private key for network service to access it.

Second method: manuel using mmc

  1. Press Windows Key + R
  2. Type mmc
  3. File => Add / Remove Snappin
  4. Add Certificates
  5. Choose computer account
  6. Right click Trusted Root Certification Authorities\Certificates
  7. Import the root certificate *.pfx (Mark it as exportable)
  8. Right click Personal\Certificates
  9. Import the client certificate *.pfx (Mark it as exportable)
  10. Right click on the imported client certificate: All tasks => Manage Private Keys
  11. Add Network Service as a user and give it full control



回答2:


There is a 3rd way that is simpler than the above ones which should work for WCF. If this service is running under the Network Service account, then you can put "protocol=https" in the service Endpoint declaration like this, which will do the acling;

<Resources>
  <Endpoints>
  <Endpoint Name="ServiceEndpoint" Protocol="https" />
</Endpoints>

Optionally you can return a certificate to the client and also run this service under a different security privilege. See here for more details https://docs.microsoft.com/en-us/azure/service-fabric/service-fabric-application-runas-security#assign-a-security-access-policy-for-http-and-https-endpoints



来源:https://stackoverflow.com/questions/43473215/calling-wcf-over-https-in-service-fabric-the-request-was-aborted-could-not-cr

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!