Products often need to talk to third party services like AWS. Many times, the best bad option available is to copy some type of API key to the device's file system. As an embedded engineer, I work under a simple assumption:
Someone will eventually hack my device
This article explores a way to provision devices without API keys.
Before going into the details, let's step back and think about the things a FoundriesFactory already has in place:
- Control of their Public Key Infrastructure (PKI)
- Each registered device has a unique x509 keypair that can be stored in an HSM making it much harder for someone to steal.
This is how devices authenticate with the FoundriesFactory device-gateway. However, since customers control their PKI, they can build similar services for their fleet.
How it works
There are two parts to connecting a device to a new web service.
The device has to trust the connection. The web server must provide an SSL certificate the device trusts. This is done one of two ways. A web service might use TLS certificates from LetsEncrypt. Alternatively, a web service could use a TLS certificate signed by your Factory's CA. In the case of the latter, the device will have a
/var/sota/root.crt
that can be used to validate the server's certificate.The server has to trust the device. The device must provide proof to the server that it's the owner of a client certificate (
/var/sota/client.pem
) that's been signed by a CA the server trusts.
Once connected, the server knows the device by its UUID (this is set in the device's certificate) and can proceed with business logic.
In Nginx terms
Roughly speaking, this can be done in Nginx by setting ssl_client_certificate
to the proper certificate chain and ssl_verify_client on
. The certificate chain is pretty easy to grab from the Factory. Run fioctl keys ca show
and create a file with:
- The Factory root certificate
- The device authentication certificates
Creating a TLS certificate
You can create your own TLS certificate if you have access to the Factory's PKI files. This will be a directory with files like: factory_ca.key, factory_ca.pem, and sign_tls_csr
. You can then run this script to create the files needed by Nginx:
#!/bin/sh -e
if [ $# -ne 1 ] ; then
echo "ERROR: $0 <domain-name>"
exit 1
fi
domain=$1
key=${domain}.key
cert=${domain}.pem
chain=${domain}-ca-chain.pem
cat >ca.cnf <<EOF
[req]
prompt = no
distinguished_name = dn
req_extensions = ext
[dn]
CN = $domain
[ext]
keyUsage=critical, digitalSignature, keyEncipherment, keyAgreement
extendedKeyUsage=critical, serverAuth
subjectAltName=DNS:${domain}
EOF
openssl ecparam -genkey -name prime256v1 | openssl ec -out $key
openssl req -new -config ca.cnf -key $key -out $cert.csr
rm ca.cnf
./sign_tls_csr $cert.csr > $cert
rm $cert.csr
cat factory_ca.pem local-ca.pem > ${chain}
Nginx configuration
A simple Nginx configuration that could serve a device looks like:
server {
listen 443 ssl;
server_name <YOUR DOMAIN NAME>;
ssl_protocols TLSv1.2;
ssl_certificate /certs/<YOUR DOMAIN NAME>.pem;
ssl_certificate_key /certs/<YOUR DOMAIN NAME>.key;
ssl_client_certificate /certs/<YOUR DOMAIN NAME>-ca-chain.pem;
ssl_verify_client on;
ssl_ciphers 'ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-SHA38';
ssl_prefer_server_ciphers on;
error_log /var/log/nginx/error.log debug;
location / {
return 200 'welcome';
}
}
You could then log into a device and try out the service with:
# With aklite (will use HSM if configured):
sudo aktualizr-lite get -u https://<domain-name>/
# With curl (no HSM, but handy for debug):
sudo -s
cd /var/sota
curl -v --cacert ./root.crt --cert ./client.pem --key ./pkey.pem YOUR_URL
Conclusion
Now we understand the "trick" for getting data securely to a device. A web service can be built to deliver API tokens or ephemeral credentials without a device having to store them. In the next articles, I'll share concrete examples built on this idea.