Using HTTPS with Philips Hue API V2

Cover Image for Using HTTPS with Philips Hue API V2

Introduction

Philips Hue's version 2 API is out, and one major difference is that it only supports HTTPS. This post will explain how to connect to the new API over HTTPS, using Node.js and Axios. This could also be useful for anyone trying to make HTTPS requests with Node.js and needing to validate with a specific CA (certificate authority) certificate or do common name validation.

Background

By default when making an HTTPS request to a server from Node.js, the server's certificate will be verified against a list of supplied CA certificates. If that verification fails then the connection will be rejected. Often times this is not an issue because the HTTPS connection is made with a server that has a public SSL certificate, and Node.js automatically includes a list of root CA certs that can verify these types of public SSL certificates.

However, this verification will not work for servers with private SSL certificates. Two common cases for private certificates are:

  1. self-signed certificates while developing
  2. certificates signed by a company's private CA

This post will cover 2 different ways to get HTTPS requests working with these types of servers.

  1. Turn Off Certificate Verification

Note - Do not use this in production because it bypasses certificate security.

Certificate verification can be turned off, but this should only be done during development. I have seen some recommendations out there that will turn off verification for all requests in the process or for all requests made from a particular HTTPS library. However, I would strongly recommend only doing it for the specific request(s) where it is needed. Here is how that can be accomplished with Axios:

// Creates a standalone axios instance with certificate verification disabled.
Axios.create({
  baseURL: 'SOME_URL',
  httpsAgent: new https.Agent({
    rejectUnauthorized: false
  })
});
  1. Pass in a Custom CA Certificate List for Verification This approach assumes you have access to a CA cert that can be used for verification and is the proper way to make production HTTPS requests to servers that use certificates signed by a private CA.
// Read in certificate .pem file.
var ca = fs.readFileSync('./cert.pem');
// Creates a standalone axios instance with that CA cert passed in for verification.
Axios.create({
  baseURL: 'SOME_URL',
  httpsAgent: new https.Agent({
    ca: ca
  })
});

Philips Hue HTTPS Requests

This is where everything circles back to Philips Hue's v2 API. The Hue bridge uses a certificate signed by Signify's private CA, and the .pem certificate used for verification can be found in the API documentation. (A hue developer account is required to access the documentation) That certificate can be used with example 2, above, to make HTTPS requests to newer Hue bridges.

At the time of this post, older Hue bridges have not yet been updated and are still using self-signed certificates, so they will not work with the above CA certificate. You can use the following command to see if your Hue bridge has a self-signed certificate: openssl s_client -showcerts -connect <bridgeIp>:443.

When I ran this, I saw the following lines that led me to believe my bridge was still using a self-signed certificate:

# I've replaced my actual hue bridgeId with 'HUE_BRIDGE_ID'
subject=/C=NL/O=Philips Hue/CN=HUE_BRIDGE_ID
issuer=/C=NL/O=Philips Hue/CN=HUE_BRIDGE_ID

As you can see, the subject and issuer are the same. At this point, I could have kept with disabling verification for testing, but I also noticed the following .pem certificate in the response to the above openssl command:

-----BEGIN CERTIFICATE-----
REDACTED
-----END CERTIFICATE-----

I tried saving it and using it as the CA cert in my Axios HTTPS request, and it worked!

Not Done Yet

There is one other step required to make HTTPS requests to the Hue bridge, which is common name validation. This is the process of making sure the common name on the certificate matches the bridgeId of the Hue bridge you are trying to connect to. There are a couple ways this can be done, but I went with the custom hostname verifier approach, which can be done with Axios in the following manner:

const bridgeId = "${bridgeId}";
// Creates a standalone axios instance with a custom hostname verifier.
// (aka - checkServerIdentity callback)
Axios.create({
  baseURL: 'https://${bridgeIp}/api/${bridgeUsername}',
  httpsAgent: new https.Agent({
    // Pass a custom function to be used to verify the certificate common name.
    checkServerIdentity: (hostname: string, cert: PeerCertificate) => {
      // Make sure the peer certificate's common name is equal to 
      // the Hue bridgeId that this request is for.
      if (cert.subject.CN === bridgeId.toLowerCase()) {
        console.log("Successful server identity check!");
        return undefined;
      } else {
        return new Error("Server identity check failed. CN does not match bridgeId.");
      }
    }
  })
});

Wrapping Up

Combining the two parts, CA certificate verification and common name validation, for making Philips Hue HTTPS requests gets you the following Axios client setup:

var ca = fs.readFileSync('./cert.pem');
const bridgeId = "${bridgeId}";
Axios.create({
  baseURL: 'https://${bridgeIp}/api/${bridgeUsername}',
  httpsAgent: new https.Agent({
    ca: ca,
    checkServerIdentity: (hostname: string, cert: PeerCertificate) => {
      if (cert.subject.CN === bridgeId.toLowerCase()) {
        console.log("Successful server identity check!");
        return undefined;
      } else {
        return new Error("Server identity check failed. CN does not match bridgeId.");
      }
    }
  })
});