GridTrust Server Setup
The GridTrust Server acts as the repository for software updates and software signatures. The server additionally verifies authentication of the GridTrust devices via the device’s PUF before issuing authorization for a software update to occur. The server itself is not connected to a PUF.
This implementation guide will emphasize the implementation of the GridTrust server and GridTrust native device. The third component, the interfacing device (a feeder protection relay), has a nearly identical implementation to the native device.
Devices Utilized:
Dell Precision 5560 Laptop
Software Utilized:
Operating System – Fedora 37
Reverse Proxy – Nginx
Docker / Docker-Compose
Database – PostgreSQL
Server Configuration
We utilized Fedora 37 for our GridTrust implementation. Newer versions of Fedora and other Linux distributions should work provided there is support for the software packages used, but have not been tested by us.
After installing Fedora onto the target computer, we require several software packages.
1. Install Docker
-
- https://docs.docker.com/desktop/install/fedora/
- On Fedora, you must separately install the docker-compose package using sudo dnf install docker-compose (after completing the installations at the link above)
2. Install Rust
-
- https://developer.fedoraproject.org/tech/languages/rust/rust-installation.html
- Install Rust with RustUp, per the link above
- Afterwards, your system may not have the required C compiler packages needed to compile and run Rust projects
- To install backend C compilation software: sudo dnf install make automake gcc gcc-c++ kernel-devel
3. Install OpenSSL: sudo dnf -y install openssl openssl-devel
4. Create a PostgreSQL Container
-
- https://tms-dev-blog.com/postgresql-database-with-rust-how-to/
- Follow the steps to start the PostgreSQL container, don’t create a new Rust project
- Check the GitHub link for the provided configuration file (docker-compose.yaml)
- In the tutorial, the Docker container is named rust-postgresql-tutorial_postgres_1, but the container using the configuration file on GitHub is named server_postgres_1
- https://tms-dev-blog.com/postgresql-database-with-rust-how-to/
5. Set Up PostgreSQL Database
-
-
- If applicable, start the Docker daemon: sudo systemctl start docker
- Log into the docker PostgreSQL container: sudo docker exec -ti <database name> bash
- Enter the PostgreSQL terminal: \psql
- If the above command does not work due to the error failed: FATAL: role “root” does not exist, then instead use the command psql -d postgres -U dboperator
- When setting up the database for the first time, run one-by-one the commands in the GitHub-provided file sql_setup
- Edit AES keys and counter files as desired
- Note: after running the SQL setup commands, there will be 3 databases: template0, template1, and postgres
- We are concerned with postgres only. Select this database by typing the PSQL command \c postgres and list the relations in the database with \d
- Should see a table named substationdevices and a sequence named substationdevices_id_seq
- Verify the table contents are correct using the PSQL command SELECT * FROM substationdevices;
- If you need to edit a value in the table, the example PSQL command is as follows: UPDATE substationdevices SET key=’00000000000000000000000000000001′ WHERE UUID=’1′;
- Accessing the database in future runs after it has already been setup:
- Ensure the Docker daemon is running: sudo systemctl start docker
- Enter the Docker container: sudo docker exec -ti server_postgres_1 bash
- Access the PostgreSQL database: psql -d postgres -U dboperator
- Exit the database with \q and exit the container with exit
-
6. The server file structure is as follows. This guide focuses on setting up GridTrust with the temperature sensor, so the files below pertain to the temperature sensor.
By default, the paths are relative to the root directory of the Rust project created in step 8 (the same directory containing Cargo.toml).
If these paths are changed, the Rust code must be changed to reflect that.
These files are generated in subsequent steps:
-
- ./update_files/tempsensor/util_sign64.txt
- ./update_files/tempsensor/vendor_sign64.txt
- ./update_files/tempsensor/update64.txt
7. Set Up Reverse Proxy
-
- **Important**: On Fedora, the SELinux policy for the server machine needs to be set to permissive. Otherwise, the Nginx server will not accept connections.
- Run sudo setenforce 0
- Install Nginx
- https://developer.fedoraproject.org/start/sw/web-app/nginx.html
- https://nginx.org/en/
- Replace the default Nginx configuration file, located at /etc/nginx/nginx.conf, with the /server/nginx.conf file found in the GitHub repository
- Restart nginx
- sudo nginx -s reload
- **Important**: On Fedora, the SELinux policy for the server machine needs to be set to permissive. Otherwise, the Nginx server will not accept connections.
-
-
- Common errors:
- [emerg] bind() to 0.0.0.0:3030 failed (13: Permission denied)
- The port 3030 is not in the SELinux allowed ports list. Resolve by running sudo semanage port -a -t http_port_t -p tcp 3030
- [emerg] bind() to 0.0.0.0:3030 failed (98: Address already in use)
- Nginx is somehow running two instances. Forcibly kill Nginx by running sudo pkill -f nginx & wait $! and start by running sudo systemctl start nginx
-
8. Create a new Rust project
-
- cargo new server
- Replace files in ../server/src with GitHub /server/code/src files
- Replace ../server/cargo.toml with GitHub server/cargo.toml
- cargo build
9. Using OpenSSL, create a self-signed certificate for Nginx and a pfx certificate for GridTrust devices
-
-
- The certificate files should be stored in /etc/nginx/ca.key and /etc/nginx/ca.crt
- OpenSSL will prompt the user with questions for the certificate info, such as country name, state or province name, etc.
- When prompted with the “Common Name” option, specify the LAN IPv4 address of the server machine (in our case, we used the LAN IPv4 172.23.2.200)
- Fill the other values out as desired, the ones besides “Common Name” do not matter from a technical standpoint
- Create a file at /etc/keys/keys.pass and input (some) passphrase, for this guide, the passphrase will be testpassword
- sudo openssl req -x509 -days 365 -newkey rsa:2048 -keyout /etc/nginx/ca.key -out /etc/nginx/ca.crt -passout file:/etc/keys/keys.pass
- Later on during the native device and interface setup, a .pfx file will be needed for authenticating the devices so that they can communicate over HTTPS
- sudo openssl pkcs12 -export -out gridtrust.pfx -inkey ca.key -in ca.crt -passin file:/etc/keys/keys.pass -passout pass:testpassword
- If the passphrase being used is not testpassword, edit the last part of the command as needed
- Ensure that gridtrust.pfx and ca.crt can be manually transferred off of the server machine; these files will be needed on the native device and interface machines
-
10. Using OpenSSL, create asymmetric key pairs for signing update files. If there are any permissions-related errors encountered, preface the commands with sudo:
-
-
- openssl genpkey -algorithm RSA -out vendor.priv.pem -pkeyopt rsa_keygen_bits:2048
- openssl rsa -pubout -in vendor.priv.pem -out vendor.pub.pem
- openssl genpkey -algorithm RSA -out utility.priv.pem -pkeyopt rsa_keygen_bits:2048
- openssl rsa -pubout -in utility.priv.pem -out utility.pub.pem
-
The public key files utility.pub.pem and vendor.pub.pem will need to be manually transferred to the native device computer in future steps.
11. The update files can be obtained from GitHub at the paths /native/updates/celsius/update64.txt
and
/native/updates/celsius/update_bin.ino.hex.
The latter is just the former file decoded from base 64 (or vice versa, the former is the latter encoded in base64).
This can be observed by decoding update64.txt and running a diff on the decoded update64.txt and update_bin.ino.hex
Sign the update file using the utility and vendor keys from the previous step (so the server is acting as the utility).
Note that we do not sign the base 64 update file, but the original binary update file. Then we convert the signatures to base 64.
-
-
- openssl dgst -sha256 -sign utility.priv.pem -out utility_sig.txt update_bin.ino.hex
- openssl dgst -sha256 -sign vendor.priv.pem -out vendor_sig.txt update_bin.ino.hex
-
Convert the utility signature to base 64:
-
-
- base64 utility_sig.txt > util_sign64.txt
- base64 vendor_sig.txt > vendor_sign64.txt
-
In the case of local updates, util_sign64.txt and vendor_sign64.txt will need to be manually transferred to the native device computer in future steps.
The non-base64 signature (utility_sig.txt, vendor_sig.txt) files are no longer needed and can be deleted.
- Make sure to place update64.txt, util_sign64.txt, and vendor_sign64.txt in the /update_files/tempsensor directory from step 6.