32

I am not using IIS, and it isn't even installed on this computer. I also don't any app.config files or web.config files in my console hosted WCF REST service. But I would like to try and get HTTPS running on the host console application:

class Program
{
    static void Main(string[] args)
    {
        string baseAddress = "http://" + Environment.MachineName + ":8000/Service";
        ServiceHost host = new ServiceHost(typeof(Service), new Uri(baseAddress));
        //WebHttpBinding binding = new WebHttpBinding();
        //binding.Security.Mode = WebHttpSecurityMode.Transport;
        host.AddServiceEndpoint(typeof(IService), new WebHttpBinding(), "").Behaviors.Add(new WebHttpBehavior());
        host.Open();

        Console.WriteLine("Host opened");
        Console.ReadLine();

Is there a way I can have my service running in HTTPS?

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Kirsty White
  • 1,210
  • 3
  • 26
  • 54
  • 5
    http://stackoverflow.com/questions/3140526/wcf-https-vs-http has an example console program using https, and it has some info on generating a self-signed cert for use in development. – E.Z. Hart Apr 23 '12 at 02:43
  • That is wsHttpBinding not webHttpBinding, thats pure wcf mine is wcf exposed as rest. Not sure it will make a difference but those are quite complex looking and I wouldnt know where to begin? +1 for nice find tho. – Kirsty White Apr 23 '12 at 03:01
  • The below-given answer(s) _should_ provide complete and robust solutions to this problem, including step-by-step advice. Do note that a general solution exists to this problem ([stunnel](http://www.stunnel.org/)) for situations in which applying SSL or TLS is impractical or impossible in your underlying application. This is especially useful in cases where the official solution isn't working effectively or where testing connection parity is necessary, so I recommend keeping it in your bag of tricks for times when that proves useful. – MrGomez Apr 30 '12 at 21:25
  • [This project](http://www.codeproject.com/Articles/295831/WCF-NET-4-0-Console-Hosted-Json-Rest-Secure-Http-W) does what you want, but obviously uses config files unfortunately. – ldgorman May 02 '12 at 14:06

2 Answers2

34
  1. Create and install a root authority and HTTPS certificate

    Open command prompt as Administrator:

    Create folder C:\Certs and navigate to it.

    #Root Authority
    makecert.exe -r -pe -n "CN=My Root Authority" -ss CA -sr LocalMachine -a sha1 -sky signature -cy authority -sv CA.pvk CA.cer
    
    #Certificate
    makecert.exe -pe -n "CN=localhost" -a sha1 -sky exchange -eku 1.3.6.1.5.5.7.3.1 -ic CA.cer -iv CA.pvk -sp "Microsoft RSA SChannel Cryptographic Provider" -sy 12 -sv server.pvk server.cer
    
    #key
    pvk2pfx.exe -pvk server.pvk -spc server.cer -pfx server.pfx
    

    **Default location for makecert and pvk2pfx is C:\Program Files (x86)\Microsoft SDKs\Windows\v7.0A\bin

  2. Install certificates

    From the command line:

    certmgr.exe -add CA.cer -r LocalMachine -s CertificateAuthority

    certmgr.exe -add server.pfx -r LocalMachine -s My -all

    From MMC:

    Open up MMC by going to command prompt and type MMC. This will open blank MMC console. Click add/remove snap in. Add Certificates and choose Computer Account / Local computer.

    Navigate to Intermediate Certification Authorities / Certificates. Right Click and choose import. Navigate to the folder where you have creatd CA.cer file and click to import.

    Navigate to Personal / Certificates and right click Import. Locate your server.pfx file (you will need to select PFX from list of available extensions) and import this file. When done open the certificate by double clicking and note its thumbprint under Details. Paste this into Notepad and remove extra ? at the beginning and remove spaces.

    To get the certificate of server thumbprint you can run this in PowerShell:

    $getThumb = Get-ChildItem -path cert:\LocalMachine\TrustedPeople | where { $_.Subject -match "CN=localhost" }
    $getThumb.thumbprint
    
  3. Register and map WCF port with netsh

    Map to WCF port

    netsh http add sslcert ipport=0.0.0.0:8000 certhash=73269e9b554f58d75e77880f5ff72b50c8d724ee appid={e2eaacd9-92e6-43cc-b51c-7a7887149607}
    
    appid - any GUID
    certhas - this is the thumb print from the step 2
    
  4. Setup your host

    Set to HTTPS and enable transport security:

    string baseAddress = "https://" + Environment.MachineName + ":8000/Service";
    var binding = new WebHttpBinding();
    binding.Security.Mode = WebHttpSecurityMode.Transport;
    

Detailed references

And if you run into problems with add sslcert:

Community
  • 1
  • 1
Petar Vučetin
  • 3,555
  • 2
  • 22
  • 31
  • This is just to get service talking.You can keep adding to the security stack e.g. binding.Security.Transport.ClientCredentialType = HttpClientCredentialType.None; just like any other non-self hosted service. – Petar Vučetin Apr 29 '12 at 17:25
  • how do you make cert where do you type it? – Kirsty White Apr 29 '12 at 18:03
  • ok when I try makecert it says Error: Save encoded certificate to store failed => 0x5 (5) – Kirsty White Apr 29 '12 at 18:09
  • if I change to CURRENTUSER rather than localmachine it works, but then the netsh throws an error The paramter is incorrect? And im probably going to hazard a guess that the third command wont work either. – Kirsty White Apr 29 '12 at 18:26
  • Yes, will that cause a problem? – Kirsty White Apr 29 '12 at 19:12
  • I tryed this on a virtual pc ran it as admin aswell but adding a cert failed with an error 1312? – Kirsty White Apr 29 '12 at 19:20
  • let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/10669/discussion-between-petar-vucetin-and-kirsty-white) – Petar Vučetin Apr 29 '12 at 19:25
  • pvk2pfx -pvk server.pvk -spc server.cer -pfx server.pfx Error File not found (Error Code = 0x80070002) – Kirsty White Apr 29 '12 at 19:37
  • @KirstyWhite this happens because your files are not in the folder you run the command – radu florescu Apr 15 '14 at 13:13
  • 1
    Can't get this to work for the life of me...Discovering the service on the client side says "the machine actively refuses it or the certificate is not configured" – arviman Dec 29 '15 at 12:21
  • Regarding step 3, is there a way to avoid this? It would seem possible and desirable for the app.config to reference the cert (thumbprint perhaps) and just reference it out of the certificate store without needing this configuration step. (I'm creating an installer and trying to simplify the story.) – Christopher Painter Aug 03 '16 at 23:55
4

Create a new console app project in VS.NET 2010. Now add reference to the dll's

 a.  System.ServiceModel
 b.  System.ServiceModel.Web
 c.  System.Runtime.Serialization

The Program.cs Main method has the following code

public class Program
    {
        public static void Main(string[] args)
        {
            Uri baseAddress = new Uri("https://"+Environment.MachineName+":54321/hello");
            using (ServiceHost host = new ServiceHost(typeof(HelloWorldService), baseAddress))
            {                
                WebHttpBinding web = new WebHttpBinding();
                web.Security.Mode = WebHttpSecurityMode.Transport;                
                host.AddServiceEndpoint(typeof(IHelloWorldService), web, "").Behaviors.Add(new WebHttpBehavior());                                
                host.Credentials.ServiceCertificate.Certificate = (X509Certificate2)GetX509Certificate();                               
                host.Open();
                Console.WriteLine("The service is ready at {0}", baseAddress);
                Console.WriteLine("Press <Enter> to stop the service.");
                Console.ReadLine();                
                host.Close();
            }
        }

        private static X509Certificate GetX509Certificate()
        {
            X509Store store = new X509Store(StoreName.My, StoreLocation.LocalMachine);
            store.Open(OpenFlags.OpenExistingOnly);
            X509Certificate certificate = null;
            X509Certificate2Collection cers = store.Certificates.Find(X509FindType.FindBySubjectName, "localhost", false);            
            if (cers.Count > 0)
            {
                certificate = cers[0];
            }
            store.Close();
            return certificate;
        }
    }

[ServiceContract]
    public interface IHelloWorldService
    {
        [WebGet(UriTemplate="SayHello/{name}")]
        string SayHello(string name);
    }

    public class HelloWorldService : IHelloWorldService
    {
        public string SayHello(string name)
        {
            return string.Format("Hello, {0}", name);
        }
    }

Now we create certificate by creating a batch file of the below commands (taken from MSDN) and executing it from VS.NET command prompt:

echo off
setlocal

call :setscriptvariables %1
IF NOT DEFINED SUPPORTED_MODE call :displayusage
IF DEFINED SUPPORTED_MODE call :cleancerts
IF DEFINED SETUP_SERVICE call :setupservice
IF DEFINED SETUP_CLIENT call :setupclient
GOTO end

:cleancerts
REM cleans up certs from previous runs.    
certmgr.exe -del -r CurrentUser -s My -c -n %CLIENT_NAME%
certmgr.exe -del -r CurrentUser -s TrustedPeople -c -n localhost
certmgr.exe -del -r LocalMachine -s My -c -n localhost
certmgr.exe -del -r LocalMachine -s TrustedPeople -c -n %CLIENT_NAME%
certmgr.exe -put -r LocalMachine -s My -c -n %COMPUTER_NAME% computer.cer
IF %ERRORLEVEL% EQU 0 (
   DEL computer.cer       
   pause
   certmgr.exe -del -r LocalMachine -s My -c -n %COMPUTER_NAME%
)

:cleanupcompleted   

GOTO :EOF

:setupclient
makecert.exe -sr CurrentUser -ss MY -a sha1 -n CN=%CLIENT_NAME% -sky exchange -pe

IF DEFINED EXPORT_CLIENT (        
    certmgr.exe -put -r CurrentUser -s My -c -n %CLIENT_NAME% client.cer
) ELSE (        
    certmgr.exe -add -r CurrentUser -s My -c -n %CLIENT_NAME% -r LocalMachine -s TrustedPeople
)
GOTO :EOF

:setupservice
makecert.exe -sr LocalMachine -ss MY -a sha1 -n CN=%SERVER_NAME% -sky exchange -pe

IF DEFINED EXPORT_SERVICE (       
    certmgr.exe -put -r LocalMachine -s My -c -n %SERVER_NAME% service.cer
) ELSE (        
    certmgr.exe -add -r LocalMachine -s My -c -n %SERVER_NAME% -r CurrentUser -s TrustedPeople
)
GOTO :EOF

:setscriptvariables
REM Parses the input to determine if we are setting this up for a single machine, client, or server
REM sets the appropriate name variables
call :setcomputername
IF [%1]==[] CALL :singlemachine
IF [%1]==[service] CALL :service
IF [%1]==[client] CALL :client

set CLIENT_NAME=client.com

GOTO :EOF

:singlemachine    
SET SUPPORTED_MODE=1
SET SETUP_CLIENT=1
SET SETUP_SERVICE=1
SET SERVER_NAME=localhost
GOTO :EOF

:service    
SET SUPPORTED_MODE=1
SET SETUP_SERVICE=1
SET EXPORT_SERVICE=1
SET SERVER_NAME=%COMPUTER_NAME%
GOTO :EOF

:client   
SET SUPPORTED_MODE=1
SET SETUP_CLIENT=1
SET EXPORT_CLIENT=1
GOTO :EOF

:setcomputername
REM Puts the Fully Qualified Name of the Computer into a variable named COMPUTER_NAME
for /F "delims=" %%i in ('cscript /nologo GetComputerName.vbs') do set COMPUTER_NAME=%%i
GOTO :EOF

:displayusage
ECHO Correct usage:
ECHO     Single Machine - Setup.bat
ECHO     Client Machine - Setup.bat client
ECHO     Service Machine - Setup.bat service
:end

Now open Microsoft Management Console and Select File --> Add/Remove Snap-in to add the Certificates - Current User and Certificate - Local Machine stores

Navigate to Certificate - Local Machine Personal Store to find a server certificate called localhost (self signed) created and installed.

Now Open your IIS and right click on the default website to add HTTPS binding to it with your port number matching that you have defined in your console app (for my its 54321) and select the certificate to be "localhost" ( the certificate that was created in the above steps) and click "OK" and "Close"

enter image description here

Now start your Console app to have your service running and now open fiddler and perform a GET request as shown:

GET https://rajeshwin7:54321/hello/sayhello/rajesh HTTP/1.1
User-Agent: Fiddler
Host: rajeshwin7:54321

Now you get back a response as below:

HTTP/1.1 200 OK
Content-Length: 90
Content-Type: application/xml; charset=utf-8
Server: Microsoft-HTTPAPI/2.0
Date: Fri, 04 May 2012 14:51:25 GMT

<string xmlns="http://schemas.microsoft.com/2003/10/Serialization/">Hello, rajesh</string>

When no IIS is present open up command prompt and enter the following at the command prompt to do a http port mapping for ssl certificate using the netsh tool in winvista and above OS and httpcfg for winxp.

C:\> netsh http add sslcert ipport=0.0.0.0:54321 certhash=6797aea29440de9389bc636e15a35b741d8c22a3 appid={2e80948d-9ae6-42c9-ad33-294929333965}

certhash -- Thumbprint id of the certificate created above. The thumbprint id can be obtained by opening up the Microsoft Management Console - Add/remove snap in for certificate store of computer account on local machine and then navigate to personal store to find the certificate (assuming it has been installed as given above) and then double click on the certificate and navigate to the details tab to find the thumbprint id as one of the properties(just copy it to use in the above netsh command by removing the spaces)

appid -- is the guid associated with your application that can be found in your assembly.cs file in your project properties folder as shown below:

enter image description here

Now to clean up the certificate create a bathc file with the below commands and execute it using Vs.NET command prompt:

echo off
setlocal
set CLIENT_NAME=client.com
call :setcomputername
call :cleancerts
DEL client.cer > NUL 2>&1
DEL service.cer > NUL 2>&1
GOTO end

:cleancerts
REM cleans up certs from previous runs.
certmgr.exe -del -r CurrentUser -s My -c -n %CLIENT_NAME%
certmgr.exe -del -r CurrentUser -s TrustedPeople -c -n localhost

certmgr.exe -del -r LocalMachine -s My -c -n localhost
certmgr.exe -del -r LocalMachine -s TrustedPeople -c -n %CLIENT_NAME%
certmgr.exe -put -r LocalMachine -s My -c -n %COMPUTER_NAME% computer.cer
IF %ERRORLEVEL% EQU 0 (
   DEL computer.cer
   pause
   certmgr.exe -del -r LocalMachine -s My -c -n %COMPUTER_NAME%
)

:cleanupcompleted
GOTO :EOF

:setcomputername
REM Puts the Fully Qualified Name of the Computer into a variable named COMPUTER_NAME
for /F "delims=" %%i in ('cscript /nologo GetComputerName.vbs') do set COMPUTER_NAME=%%i
GOTO :EOF

:end

You can remove the ssl certificate mapping to a port using netsh command as below:

c:\> netsh http delete sslcert ipport:0.0.0.0:54321
Rajesh
  • 7,766
  • 5
  • 22
  • 35