"Romans_I_XVI" wrote:
Is using cacert.pem from the legacy SDK still the appropriate method?
I don't see why not. However, you have to use it correctly to get the desired level of security.
"Romans_I_XVI" wrote:
To set up my server I used the instructions found here. - https://matoski.com/article/node-express-generate-ssl/
That's not a very good example. He generates a private key used to sign a Certificate Signing Request for his Certificate Authority, then generates a self-signed certificate for the Certificate Authority -- none of which get used when he generates the self-signed server certificate.
What you need to do depends on what level of authentication you are aiming for:
Server Authentication/EncryptionThe
authentication part is when you want your Roku channel code to verify that it is communicating with YOUR server, and not some hacker trying to direct traffic from your channel running on his Roku, to his own server, so he can figure out your API. The
encryption part prevents anyone from eavesdropping on the connection between your channel running on a user's Roku and your server at any point in the connection.
You can use a self-signed server certificate for this. Your server must be configured to access both the self-signed server certificate and the private key used to sign the server certificate.
The only requirement for your Roku channel to implement Server Authentication is that ifUrlTransfer.SetCertificatesFile() is called to specify a file containing a certificate (chain) that can verify the authenticity of your server certificate, and an "https" url scheme is used. Normally, one would use a server certificate issued by a trusted Certificate Authority (CA), and specify "common:/certs/ca-bundle.crt", which contains a bundle of well-known, publicly-available trusted CA certificates. However, when using a self-signed server certificate, you must specify a file containing the certificate of the self-signed CA used to sign your certificate (if you created your own CA certificate used to sign the server certificate -- or the server certificate itself, if it was purely self-signed, rather than signed by your own CA, as in my example below).
Do not call EnablePeerVerification() or EnableHostVerification() if you're trying to achieve a high level of server authentication. Use the defaults, which are True in both cases. EnablePeerVerification(False) disables the check that the server certificate was signed by a trusted authority, allowing a hacker to present a server certificate pretending to be from your domain, but which could have been signed by the hacker's own CA. EnableHostVerification(False) disables the check that the server certificate was issued to the same domain name used to identify your own server, allowing a hacker to present a certificate signed by a trusted CA, but which was issued for the hacker's domain rather than your own. The only time you might want to call EnablePeerVerification(False) would be if for some reason you're unable to install your server certificate in your Roku channel. In this case you would not have an authenticated connection with the server (and would therefore be subject to 'man in the middle' attacks), although your connection would still be encrypted (if using "https"), preventing eavesdropping.
Client AuthenticationThis is when you want to ensure that all traffic your server receives comes from an actual Roku device, and not some hacking tool trying to get information from your server.
In this case, the server must be configured for client certificate authentication and have access to the Roku certificate, and the Roku device must be instructed to send its client certificate to the server. The Roku certificate installed in your server is obtained from the Roku SDK file, cacert.pem. All you need do in the Roku channel to ensure the client certificate is sent is call ifUrltransfer.InitClientCertificates().
Roku Developer Authentication^ That's not a real term -- but I'm referring to verifying that the Roku channel communicating with your server is YOUR Roku channel, and not a Roku channel developed by some hacker.
If you call ifHttpAgent.AddHeader("X-Roku-Reserved-Dev-Id", ""), then any request sent from your Roku channel to your server will contain an HTTP X-Roku-Reserved-Dev-Id header with something that identifies YOU as the developer of the channel. Your server can then read its request headers to extract the developer id. Note that when the channel is side-loaded, it uses a different developer id from that used by a channel installed from the Channel Store. Your server code should take that into account.
Example incorporating all 3 methodsGenerate the private key to be used to sign your self-signed server certificate:
openssl genpkey -out server-key.pem -algorithm RSA -pkeyopt rsa_keygen_bits:2048
Generate the self-signed server certificate:
openssl req -out server.crt -new -key server-key.pem -nodes -x509 -days 3650
Answer the questions used to specify the certificate fields. In particular, ensure that the Common Name (CN) field is the domain name of your server. Make sure you're using an actual domain name rather than an IP address for the server. If you don't have one, you can get a free '.tk' domain from
dot.tk.
Put the certificates where your server can access them:
ssl/server-key.pem - server private key
ssl/server.crt - server self-signed certificate
ssl/cacert.pem - Roku SDK certificate
Configure your server, e.g. for node.js:
const https = require('https');
const fs = require('fs');
// Roku application's Developer ID
const rokuDevId = '22c6fabab75e456f25e7e12345e4242c6c1de443';
const options = {
// Server's private key
key: fs.readFileSync('ssl/server-key.pem'),
// Server's self-signed certificate
cert: fs.readFileSync('ssl/server.crt'),
// Roku's certificate
ca: fs.readFileSync('ssl/cacert.pem'),
// Require Roku to send a client certificate
requestCert: true,
// Reject if the client certificate received is not the one in cacert.pem
rejectUnauthorized: true
};
https.createServer(options, (req, res) => {
console.log ('user-agent:', req.headers['user-agent']);
console.log ('x-roku-reserved-dev-id:', req.headers['x-roku-reserved-dev-id']);
if (req.client.authorized &&
req.headers['x-roku-reserved-dev-id'] === rokuDevId) {
res.writeHead(200);
res.end('Hello, World!\n');
}
else {
res.writeHead(403);
res.end('Unauthorized\n');
}
}).listen(443);
Put the self-signed server certificate in your Roku package:
/certs/server.crt
Example Roku code:
Sub Main ()
port = CreateObject ("roMessagePort")
ut = CreateObject ("roUrlTransfer")
ut.SetPort (port)
ut.SetUrl ("https://myserver.tk/")
ut.SetCertificatesFile ("pkg:/certs/server.crt")
ut.InitClientCertificates ()
ut.AddHeader("X-Roku-Reserved-Dev-Id", "")
If ut.AsyncGetToString ()
While True
msg = Wait (0, port)
If Type (msg) = "roUrlEvent"
If msg.GetInt () = 1
responseCode = msg.GetResponseCode ()
failureReason = msg.GetFailureReason ()
If responseCode = 200
data = msg.GetString ()
Print "Data received: "; data
Else
Print "Response code: "; responseCode
Print "Failure reason: "; failureReason
End If
Else
Print "Url transfer did not complete"
End If
Exit While
End If
End While
Else
Print "AsyncGetToString failed"
End If
End Sub