[ Impressum ]

LiveCode Server for HTTPS Server built with Node.js

www.Rozek.de > LiveCode Server > HTTPS
A former note explained, how to build your own HTTP server using Node.js [1] and Express.js [2] together with the SimpleCGI "Middleware", which recognizes CGI requests and forwards them to "LiveCode Server" [3] as its associated CGI processor.

Unfortunately, CGI scripts are sometimes critical to safety, which is why they should not always be freely accessible. For that reason, this note explains how to create a server which also supports HTTPS and authentication of requesting users.

Overview

  • A Certificate as a Basis for HTTPS
  • An HTTPS-Server built with Node.js
  • Authenticating Users

A Certificate as a Basis for HTTPS

First of all, anyone who wants to set up an HTTPS server, needs a so-called TLS/SSL certificate. Without going into details too much: such a certificate
  1. identifies the server (for which the certificate has been issued) - that way, the browser knows that it really communicates with the requested server;
  2. contains a public key, which plays a role in connection set-up.
Each certificate also requires a key pair (consisting of a public and a non-public ("private") key), which plays an essential role in the connection between browser and server (in this phase, inter alia, the concrete key is negotiated, with which the communication over the current connection is actually encrypted).

As long as your HTTPS server is a private one, you may easily create such a (so-called "self-signed") certificate yourself.

However, since really anybody can generate such a certificate, one will take care that certificates for public servers were issued by an official agency or at least signed by such. Alternatively, you may also distribute the "self-signed" certificate you created to all "customers" of your server in advance - provided that you already know this group of people. The recipients will then have to import your certificate into their browsers. Without such a preparatory measure, a self-signed certificate is worthless or even dangerous as it pretends a level of security which does not exist in reality.

Generating a "self-signed" Certificate

The basic procedure for creating a self-signed certificate is very well described in the corresponding Node.js documentation:
  1. change to the directory containing the WebServer for "LiveCode Server" (in this way the files to be created below will already be stored at the right place)
     
  2. generate a private key for the certificate
     
    openssl genrsa -out WebServer-Key.pem 2048
     
  3. create a self-signed certificate for your server
     
    openssl req -new -x509 -days 365 -subj '/CN=127.0.0.1' -key WebServer-Key.pem -out WebServer-Certificate.pem
     
    this certificate
    • belongs to the local host (because of CN=127.0.0.1) and
    • is valid for one year (because of -days 365)
       
  4. check the contents of your certificate with
     
    openssl x509 -text -in WebServer-Certificate.pem
The last command returns a slightly elongated output in the following format:

Certificate: Data:
Version: 3 (0x2)
Serial Number: 17894222022604898032 (0xf8550c1f35563ef0)
Signature Algorithm: sha1WithRSAEncryption
Issuer: CN=127.0.0.1
Validity
Not Before: Feb 8 04:53:25 2014 GMT
Not After : Feb 8 04:53:25 2015 GMT
Subject: CN=127.0.0.1
Subject Public Key Info:
Public Key Algorithm: rsaEncryption
Public-Key: (2048 bit)
Modulus:
00:be:01:f9:bf:5c:66:3a:b0:58:af:69:16:b5:f6:
1e:1f:29:d7:65:62:0b:ac:21:2b:c3:71:42:d7:8c:
90:4d:3d:b4:4b:55:f9:6a:43:e9:db:17:bd:eb:4c:
1d:7e:a8:ab:a0:fc:53:0a:e6:b0:7b:25:7e:ad:fd:
ec:85:0b:c1:e9:c1:0d:20:d8:9b:be:43:fc:e5:81:
09:e0:b9:d6:9b:e5:ea:1a:56:9a:fc:bb:27:35:23:
b8:7c:6c:a9:57:17:a9:e1:60:2e:39:83:5a:fb:e6:
72:bd:43:99:bc:51:95:72:9c:81:55:48:0a:4f:82:
ac:1a:7f:12:30:fe:46:47:21:0d:3c:c7:95:65:39:
d6:da:43:3f:b8:5b:c5:ed:c6:31:2b:14:42:5d:62:
0d:91:bc:8b:5b:45:09:6b:c7:2c:1e:5e:f3:b4:91:
5a:3e:ec:89:a2:de:b8:0d:81:2b:1c:c4:f0:aa:89:
ca:bd:79:a0:e1:93:ad:0e:a0:42:d6:df:6c:00:06:
4b:01:2d:4e:92:57:8d:80:00:65:6a:e6:51:15:4e:
3e:b3:3f:26:14:9f:d4:9e:34:6c:00:62:cb:c0:78:
e4:7e:8a:e3:93:40:78:d4:d7:74:57:fb:bc:ce:1d:
18:76:24:b7:f4:a4:68:6e:4b:1f:7f:00:60:37:18:
1b:4f
Exponent: 65537 (0x10001)
X509v3 extensions:
X509v3 Subject Key Identifier:
3D:D4:5F:5E:90:E5:A9:8A:D3:A2:BB:96:A6:98:E0:BD:7C:F8:36:CF
X509v3 Authority Key Identifier:
keyid:3D:D4:5F:5E:90:E5:A9:8A:D3:A2:BB:96:A6:98:E0:BD:7C:F8:36:CF


X509v3 Basic Constraints:
CA:TRUE
Signature Algorithm: sha1WithRSAEncryption
23:4c:18:6a:6b:9a:e0:88:61:9f:2c:77:48:5b:26:6d:c5:c8:
b1:b7:0d:1b:ec:dd:29:a5:26:27:aa:58:f8:25:e4:e6:e4:16:
ba:b1:df:b6:8a:43:d3:37:ec:fc:04:4d:9e:0c:dd:92:54:80:
17:34:e1:4d:61:49:a0:b6:05:5c:27:af:93:b7:38:97:12:d6:
00:82:dd:45:26:cd:54:d0:2f:99:cc:08:a6:58:86:8a:3f:6b:
a0:73:d9:3a:3d:4b:cb:4d:47:8a:fb:31:b3:46:08:45:0c:35:
80:8a:5f:b4:6f:2e:f3:82:d6:c7:63:29:2e:47:59:1e:af:ac:
99:cc:92:fb:6e:53:f1:6a:0d:29:a5:60:b4:1b:06:0c:45:4a:
f4:99:ae:11:a7:9b:6f:97:de:38:11:91:e0:36:ce:20:24:66:
b3:1b:1b:1e:78:8f:51:5a:d6:1f:96:17:85:27:8b:c3:b3:2e:
b0:33:08:17:e9:e2:fd:74:9f:8c:99:61:d8:b1:3a:79:2f:34:
b5:40:93:29:8f:1f:a6:58:88:4f:bd:96:9e:38:df:9a:8b:30:
e2:84:5e:5a:0f:67:5f:82:e6:ab:5d:8a:57:dc:3d:78:c0:ee:
54:15:b5:31:dc:c3:6b:3d:99:9f:3c:89:0a:4e:47:3f:54:2a:
c8:4f:09:38
-----BEGIN CERTIFICATE-----
MIIC+zCCAeOgAwIBAgIJAPhVDB81Vj7wMA0GCSqGSIb3DQEBBQUAMBQxEjAQBgNV
BAMMCTEyNy4wLjAuMTAeFw0xNDAyMDgwNDUzMjVaFw0xNTAyMDgwNDUzMjVaMBQx
EjAQBgNVBAMMCTEyNy4wLjAuMTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoC
ggEBAL4B+b9cZjqwWK9pFrX2Hh8p12ViC6whK8NxQteMkE09tEtV+WpD6dsXvetM
HX6oq6D8UwrmsHslfq397IULwenBDSDYm75D/OWBCeC51pvl6hpWmvy7JzUjuHxs
qVcXqeFgLjmDWvvmcr1DmbxRlXKcgVVICk+CrBp/EjD+RkchDTzHlWU51tpDP7hb
xe3GMSsUQl1iDZG8i1tFCWvHLB5e87SRWj7siaLeuA2BKxzE8KqJyr15oOGTrQ6g
QtbfbAAGSwEtTpJXjYAAZWrmURVOPrM/JhSf1J40bABiy8B45H6K45NAeNTXdFf7
vM4dGHYkt/SkaG5LH38AYDcYG08CAwEAAaNQME4wHQYDVR0OBBYEFD3UX16Q5amK
06K7lqaY4L18+DbPMB8GA1UdIwQYMBaAFD3UX16Q5amK06K7lqaY4L18+DbPMAwG
A1UdEwQFMAMBAf8wDQYJKoZIhvcNAQEFBQADggEBACNMGGprmuCIYZ8sd0hbJm3F
yLG3DRvs3SmlJieqWPgl5ObkFrqx37aKQ9M37PwETZ4M3ZJUgBc04U1hSaC2BVwn
r5O3OJcS1gCC3UUmzVTQL5nMCKZYhoo/a6Bz2To9S8tNR4r7MbNGCEUMNYCKX7Rv
LvOC1sdjKS5HWR6vrJnMkvtuU/FqDSmlYLQbBgxFSvSZrhGnm2+X3jgRkeA2ziAk
ZrMbGx54j1Fa1h+WF4Uni8OzLrAzCBfp4v10n4yZYdixOnkvNLVAkymPH6ZYiE+9
lp4435qLMOKEXloPZ1+C5qtdilfcPXjA7lQVtTHcw2s9mZ88iQpORz9UKshPCTg=
-----END CERTIFICATE-----

The most important information (for our purposes) is, however, already shown at the beginning of this output:

Issuer: CN=127.0.0.1
Validity
Not Before: Feb 8 04:53:25 2014 GMT
Not After : Feb 8 04:53:25 2015 GMT
Subject: CN=127.0.0.1

The "Issuer" indicates that the key has been issued by your own computer. "Validity" describes the validity period for the certificate and "Subject" contains the IP address of the computer for which the certificate was issued.

These were all necessary preparations and we may deal with the actual server now.

Generating an "official" Certificate

If you need a certificate that has been issued and signed by an official agency, you should contact the service provider that hosts your server.

An HTTPS-Server built with Node.js

The source code for an HTTP server with "LiveCode Server" as a CGI processor has already been shown. A variant that supports HTTPS, looks very similar:

#!/usr/bin/env node
var express = require('express');
var fs = require('fs');
var http = require('http');
var https = require('https');
var SimpleCGI = require('simplecgi');

var oneDay = 24*60*60*1000;

var WebServer = express();
WebServer.use(express.compress());
WebServer.use(express.staticCache());

WebServer.all(/^.+[.]lc$/, SimpleCGI(
'/usr/local/bin/livecode-server', __dirname + '/www', /^.+[.]lc$/
));

WebServer.use(express.static(__dirname + '/www', { maxAge:oneDay }));

WebServer.use(express.errorHandler());
http.createServer(WebServer).listen(8080); // actually starts HTTP server
https.createServer({ // dto. for HTTPS
key: fs.readFileSync(__dirname + '/WebServer-Key.pem'),
cert: fs.readFileSync(__dirname + '/WebServer-Certificate.pem')
}, WebServer).listen(8081);

Like before, save this script in file WebServer and restart the server. As a functional test, open your browser to the page

https://127.0.0.1:8081/00_SmokeTest.lc

Pay particular attention to the new protocol (https) and the new port number (8081)!

At the first contact with your server, your browser should somehow indicate that it can not verify the identity of the computer with the IP address 127.0.0.1 - this is a clear indication of a not officially signed certificate. In the general case, you should now check very carefully whether you really want to trust this server - but in our test case, the certificate is trustworthy and may be accepted.

The actual output of the smoke test is the same as under HTTP.

If you examine the CGI environment variables using

https://127.0.0.1:8081/01_EnvironmentVariables.lc

you will see that variable SERVER_PROTOCOL contains HTTPS/1.1 now - this way you can recognize secure connections in a "LiveCode Server" script.

Authenticating Users

HTTPS is the basis for a secure access to confidential or even potentially dangerous files - but server access itself continues to be free for everyone. What is still missing is an authentication of requesting users.

If you limit yourself to a single login, the server can be extended very easily:

#!/usr/bin/env node
var express = require('express');
var fs = require('fs');
var http = require('http');
var https = require('https');
var SimpleCGI = require('simplecgi');

var oneDay = 24*60*60*1000;

var WebServer = express();
WebServer.use(express.compress());
WebServer.use(express.staticCache());

WebServer.all(/^.+[.]lc$/, express.basicAuth(function(UserName,Password){
return (UserName === 'user') && (Password === 'password');
}), SimpleCGI(
'/usr/local/bin/livecode-server', __dirname + '/www', /^.+[.]lc$/
));

WebServer.use(express.static(__dirname + '/www', { maxAge:oneDay }));

WebServer.use(express.errorHandler());
http.createServer(WebServer).listen(8080); // actually starts HTTP server
https.createServer({ // dto. for HTTPS
key: fs.readFileSync(__dirname + '/WebServer-Key.pem'),
cert: fs.readFileSync(__dirname + '/WebServer-Certificate.pem')
}, WebServer).listen(8081);

Here again: please save this script to file WebServer and restart the server.

If you now want to access a page (containing a "LiveCode Server" script) again, you will be prompted for user name and password by your browser - and only after entering "user" and "password" you will get access to the desired script.

Of course, this (trivial) server has a number of disadvantages:
  1. it supports a single user only
    this problem can, for example, be circumvented by extending the term which tests user name and password;
  2. the list of foreseen users has been "hard-wired" into the server
    this problem can be solved by reading the list of authorized users from a file;
  3. any passwords are stored as plain, readable text
    from a security viewpoint, this shortcoming is a disaster - any passwords should be immediately converted, e.g., to MD5 hashes
The following script solves all these problems and reads any user data from a file called WebServer-UserList.txt:

#!/usr/bin/env node
var crypto = require('crypto');
var express = require('express');
var fs = require('fs');
var http = require('http');
var https = require('https');
var SimpleCGI = require('simplecgi');
var oneDay = 24*60*60*1000;

var WebServer = express();
WebServer.use(express.compress());
WebServer.use(express.staticCache());

WebServer.all(/^.+[.]lc$/, express.basicAuth(function(UserName,Password, next){
UserName = UserName.toLowerCase();

fs.readFile(
__dirname + '/WebServer-UserList.txt', function (Error, FileContent) {
if (Error) {
console.log('Internal Error during Authentication: ', Error);
return next(null, false);
};

var LineList = FileContent.toString().split('\n');
for (var i = 0; i < LineList.length; i++) {
if (LineList[i].replace(/ *:.*$/,'').replace(/^ */,'').toLowerCase() === UserName) {
var PasswordHash = crypto.createHash('md5').update(Password).digest('hex');
return next(null, (LineList[i].replace(/^.*: */,'').replace(/ *$/,'') === PasswordHash));
};
};

return next(null, false);
}
);
}), SimpleCGI(
'/usr/local/bin/livecode-server', __dirname + '/www', /^.+[.]lc$/
));

WebServer.use(express.static(__dirname + '/www', { maxAge:oneDay }));

WebServer.use(express.errorHandler());
http.createServer(WebServer).listen(8080); // actually starts HTTP server
https.createServer({ // dto. for HTTPS
key: fs.readFileSync(__dirname + '/WebServer-Key.pem'),
cert: fs.readFileSync(__dirname + '/WebServer-Certificate.pem')
}, WebServer).listen(8081);

Even this script is still quite short. Save it (as usual) in file WebServer and restart the server.

Although you can try it already (you even should!), you will not be able to log in as the file WebServer-UserList.txt is still missing so far. Instead, you will always get an error message (which will be displayed in the terminal window with the server command).

The file WebServer-UserList.txt should contain one user per line, in the following format:

<username>:<password-hash>

That is, each line contains
  1. the user name (letter cases are irrelevant),
  2. a colon as a separator and
  3. the MD5 hash of the user password
Such lines can be easily created using the following small LiveCode function:
function PasswordEntry UserName, UserPass
local Outcome
get binaryDecode("H*", md5digest(UserPass), Outcome)
return lower(UserName) & ":" & Outcome
end PasswordEntry

Running the command

put PasswordEntry("user","password")

from within the "Message Box" of the LiveCode development environment will, e.g., provide the following output:

user:5f4dcc3b5aa765d61d8327deb882cf99

If you enter this line in a file called WebServer-UserList.txt (in the same directory as your web server), you will be able to log into your server again.

Log-in can be done both via your browser (which will offer you a dialog window) or a URL of the form

https://user:password@127.0.0.1:8081/01_EnvironmentVariables.lc

In the list of CGI environment variables, you will see that besides variable SERVER_PROTOCOL (which still contains HTTPS/1.1) there is now also the REMOTE_USER variable set: it contains the name of the current user (in our case "user") - this way you can recognize both secure connections and successfully authenticated users in a "LiveCode Server" script.

Nota bene: calculating password hashes using LiveCode seems comfortable but is far from safe [4] - if you care about your user's security, you should therefore better upgrade to PBKDF2 (even if the hashes then have to be calculated using Node.js).

                       
Have fun with "LiveCode Server" and this HTTPS server! Creative Commons License

Bibliography

[1]
Joyent Inc.
node.js
Node.js is first and foremost a platform for extremely powerful network applications written in JavaScript. However, in combination with other technologies (such as Node-WebKit) Node.js may also be used for more than just HTTP servers.
[2]
Tj Holowaychuk
Express - node.js web application framework
Express is a lightweight web application framework for Node.js. Thanks to its modularity, Express allows for rapid development of HTTP servers based on Node.js.
[3]
(RunRev Ltd.)
LiveCode | LiveCode Server Guide
The LiveCode Server is an interpreter for LiveCode scripts that is started from the command line (does not offer any graphical user interface) and is intended primarily as a CGI processor (this way, web pages can be processed with LiveCode - thus, you do not necessarily have to learn PHP any longer).
[4]
(Defuse Security)
Secure Salted Password Hashing - How to do it Properly
If you run a server that requires a login from its users, you will have to persist these security-relevant data somehow. This web page describes the potential problems and provides appropriate solutions.