Showing posts with label https. Show all posts
Showing posts with label https. Show all posts

08 December 2016


Forcing an ASP.NET MVC site to only serve HTTPS


Why force HTTPS?

Hyper Text Transfer Protocol Secure (HTTPS) is the secure version of HTTP, the protocol over which data is sent between your browser and the website that you are connected to. The 'S' at the end of HTTPS stands for 'Secure'. It means all communications between your browser and the website are encrypted.



How to force HTTPS


ASP.NET Action Filters

ASP.NET MVC allows you to apply a [RequireHttps] attribute on individual page controllers. It also allows the attribute to be applied globally by adding code to Application_Start in the Global.asax.


The problem with the built-in RequireHttpsAttribute

In a nutshell, the problem is that it returns 302, a temporary redirection HTTP Status Code (see List of HTTP Status Codes [Wikipedia]). This is an SEO problem. A return value of 301 means "Moved Permanently" and is a hint to the search engines to update their indexes.

If the built-in attribute is used, a result similar to the one below is obtained when the page is retrieved:


curl http://www.localexample.com:4433/ -iILk
HTTP/1.0 302 Found

The desired result is:


curl http://www.localexample.com:4433/ -iILk
HTTP/1.0 301 Moved Permanently



Writing your own version of the RequireHttpsAttribute

Writing your own attribute is fairly straight-forward.

  1. Create a class that inherits from the RequireHttpsAttribute class
  2. Override the HandleNotHttpsRequest method.
  3. Add some code to handle running in your local development environment. (NB you will need to create a self-signed certificate for a dummy domain (we use www.localexample.com), install it on your machine and update your hosts file).
  4. Build the HTTPS address
  5. End the method by setting the Result property of the filterContext to a new RedirectResult that uses the HTTPS address and sets the permanent parameter to true.

Although you can apply this attribute to your controller methods individually, applying it globally minimizes the effort and ensures that nothing is missed.


Wiring up your filter configuration

You will need to create/update the FilterConfig class.

The code shown below illustrates the necessary change, which is simply the addition of your custom attribute to the global filter collection.


Code is then added to the App_Start method in the Global class to run RegisterGlobalFilters.

The code below illustrates how to do this.






29 December 2015

The Problem

Setting up an HTTPS website is generally a pain. One must acquire a certificate for use with TLS (not trivial) and then monkey around with getting the pieces in the correct format for the host technology.

When we first set up our site, the process took hours due to delays from the Certificate Authority (CA).

The other issue with CAs is that most of them do not issue free certificates.

Since we use and recommend using static websites hosted on Amazon AWS S3 Static Website Hosting using Amazon CloudFront CDN as the Content Delivery Network (CDN) with dynamic server endpoints for serving dynamic data, capturing analytics, performing business logic, etc. (we normally do this via Express on Node.js), we were interested in finding a way to quickly generate certificates and, if possible, a way that is free. 

Let's Encrypt

Let's Encrypt is a free, automated and open certificate authority. It issues 90-day certificates that can be auto-renewed via Automated Certificate Management Environment (ACME).

The technology is in beta, and unfortunately, the tools are flaky on Amazon EC2 Linux boxes. Also, the tools appear to be designed mostly for generating certificates on the same machine as the website host (not our use case). 

Let's Encrypt without sudo

Thankfully, an apparently clever chap named Daniel Roesler has created Let's Encrypt without sudo that with a bit of hacking works well for us.

CloudFront Script

First, create a directory  for the site (we do ours as a directory within the "letsencrypt-nosudo" directory).

Next, run this script and follow all of the instructions EXCEPT for the one to start a local server.

openssl genrsa 2048 > user.key && \
openssl rsa -in user.key -pubout > user.pub && \
openssl genrsa 2048 > domain.key && \
openssl req -new -sha256 -key domain.key -subj "/CN=www.YOUR_DOMAIN_NAME.com" > ./domain.csr && \
python ../sign_csr.py --public-key ./user.pub ./domain.csr > ./domain.crt && \
wget https://letsencrypt.org/certs/lets-encrypt-x1-cross-signed.pem

When you get to the server step, you will have in the prompt the pieces you need to upload the ACME file to AWS S3.

Suppose the prompt has the following in the "write" statement:

x0Cgs-62meybGnAco2rExV0y_WS1RRVwhTBBRAfaSFQ.PUN4jJ4AfNuF7Al_u6cA-dVqfLtpKYPm9LVxVEvXcao.

To pass the Let's Encrypt ACME verification, you will need to create a file named 62meybGnAco2rExV0y_WS1RRVwhTBBRAfaSFQ with the following as the contents:

x0Cgs-62meybGnAco2rExV0y_WS1RRVwhTBBRAfaSFQ.PUN4jJ4AfNuF7Al_u6cA-dVqfLtpKYPm9LVxVEvXcao

in the path YOUR_BUCKET/.well-known/acme-challenge/.

The file should be uploaded with the Content-Type: text/plain.


Once the file is uploaded and you have verified access to it. Hit <ENTER> to let Let's Encrypt verify the ACME challenge.

Once the scripts have completed, you will need to run the following command (assuming you have the AWS CLI tools installed and have your keys stored as environment variables):

aws iam upload-server-certificate \
--server-certificate-name  www.YOUR_DOMAIN.com \
--certificate-body file://domain.crt \
--private-key file://domain.key \
--certificate-chain file://lets-encrypt-x1-cross-signed.pem \
--path /cloudfront/

Express Script

Follow the steps as outlined, but start the server when prompted.

For Express, you will need a slightly different chain. Run the following command:

cat domain.crt lets-encrypt-x1-cross-signed.pem > chained.pem

Then in your node.js app (we use a custom logging script; comment out those lines if you don't have a logging module),

try {
  var fs = require("fs");
  var key = fs.readFileSync("YOUR_PATH/domain.key");
  cert = fs.readFileSync("YOUR_PATH/chained.pem");
  var https_options = {
    key: key,
    cert: cert,
  };
  var tls = https.createServer(https_options, app).listen(5001, function() {
    var host = tls.address().address;
    var port = tls.address().port;
    logger.info('App listening at https://%s:%s', host, port);
  });
}
catch (e) {
  logger.error(e);
}