Blog from October, 2024

FIDO Device Onboard

This document provides how to set up FIDO Device Onboard (FDO) on Synquacer and runs a simple demo.

Prerequisites

  • meta-ts (>=0.4) installed machine connected to the internet.

For building and installing the firmware, refer to https://linaro.atlassian.net/wiki/spaces/FLT/pages/28747595918/Firmware+Build+and+Installation+Manual#Build-firmware-for-DeveloperBox.

Overview

FDO, or FIDO Device Onboarding, establishes a standardized and secure process for manufacturers to configure devices during onboarding. The primary goal is enabling OEMs or sellers to accurately and automatically configure devices, improving efficiency and security without requiring user involvement.

Setup environment

We need to prepare the servers to ensure that the Synquacer device can communicate with FDO servers. To achieve this, we will use a Docker image that provides portability and install all the necessary FDO servers on it. On the Synquacer side, we will install the Fedora OS with the FDO client package using U-Boot UEFI HTTP Boot.

On host machine

Run Fedora docker image

docker pull fedora:39
docker run --name fdo -it -p 8080-8083:8080-8083/tcp fedora:39 /bin/bash

Install FDO packages

[root@fedora /]# dnf install -y fdo-*

Setup configurations for FDO servers

[root@fedora /]# cd /etc/fdo
[root@fedora fdo]# for i in manufacturer owner device-ca diun ; do
> fdo-admin-tool generate-key-and-cert --destination-dir keys $i
> done
[root@fedora fdo]# touch manufacturing-server.yml \
  rendezvous-server.yml \
  rendezvous-info.yml \
  owner-onboarding-server.yml \
  serviceinfo-api-server.yml
 /etc/fdo/manufacturing-server.yml
---
session_store_driver:
  Directory:
    path: /etc/fdo/stores/manufacturing_sessions/
ownership_voucher_store_driver:
  Directory:
    path: /etc/fdo/stores/owner_vouchers/
public_key_store_driver:
  Directory:
    path: /etc/fdo/stores/manufacturer_keys
bind: 0.0.0.0:8080
rendezvous_info:
- ip: 192.168.1.10
  device_port: 8082
  owner_port: 8082
  protocol: http
protocols:
  diun:
    key_path: /etc/fdo/keys/diun_key.der
    cert_path: /etc/fdo/keys/diun_cert.pem
    key_type: SECP256R1
    mfg_string_type: SerialNumber
    allowed_key_storage_types:
    - FileSystem
    - Tpm
manufacturing:
  manufacturer_cert_path: /etc/fdo//keys/manufacturer_cert.pem
  manufacturer_private_key: /etc/fdo/keys/manufacturer_key.der
  owner_cert_path: /etc/fdo/keys/owner_cert.pem
  device_cert_ca_private_key: /etc/fdo/keys/device_ca_key.der
  device_cert_ca_chain: /etc/fdo/keys/device_ca_cert.pem
 /etc/fdo/rendezvous-server.yml
---
storage_driver:
  Directory:
    path: /etc/fdo/stores/rendezvous_registered
session_store_driver:
  Directory:
    path: /etc/fdo/stores/rendezvous_sessions
trusted_manufacturer_keys_path: /etc/fdo/keys/manufacturer_cert.pem
max_wait_seconds: ~
bind: "0.0.0.0:8082"
 /etc/fdo/rendezvous-info.yml
---
- ip_address: 192.168.1.10
  deviceport: 8082
  ownerport: 8082
  protocol: http
 /etc/fdo/owner-onboarding-server.yml
---
session_store_driver:
  Directory:
    path: /etc/fdo/stores/owner_onboarding_sessions/
ownership_voucher_store_driver:
  Directory:
    path: /etc/fdo/stores/owner_vouchers/
trusted_device_keys_path: /etc/fdo/keys/device_ca_cert.pem
owner_private_key_path: /etc/fdo/keys/owner_key.der
owner_public_key_path: /etc/fdo/keys/owner_cert.pem
owner_addresses:
- transport: HTTP
  port: 8081
  addresses:
    - ip_address: 192.168.1.10
report_to_rendezvous_endpoint_enabled: false
bind: 0.0.0.0:8081
service_info_api_url: "http://localhost:8083/device_info"
service_info_api_authentication:
  BearerToken:
    token: TestAuthToken
 /etc/fdo/serviceinfo-api-server.yml
---
bind: 0.0.0.0:8083
device_specific_store_driver:
  Directory:
    path: /etc/fdo/stores/device_specific_serviceinfo
service_info_auth_token: TestAuthToken
admin_auth_token: TestAdminToken
service_info:
  initial_user:
    username: admin
    sshkeys:
    - "testkey"
  files:
  - path: /var/lib/fdo/service-info-api/files/hosts
    permissions: 644
    source_path: /etc/hosts
  - path: /var/lib/fdo/service-info-api/files/resolv.conf
    source_path: /etc/resolv.conf
  commands:
  - command: ls
    args:
    - /etc/hosts
    return_stdout: true
    return_stderr: true
  - command: ls
    args:
    - /etc/doesnotexist/whatever.foo
    may_fail: true
    return_stdout: true
    return_stderr: true
  - command: touch
    args:
    - /etc/command-testfile
  after_onboarding_reboot: false

Prepare an Ownership Voucher (OV) and Device Credential

Generate the OV and Device Credential

[root@fedora fdo]# mkdir -p ./stores/device_credentials
[root@fedora fdo]# fdo-owner-tool initialize-device \
  1234 \
  ./stores/owner_vouchers/ownership_voucher_synquacer \
  ./stores/device_credentials/device_credential_synquacer \
  --device-cert-ca-chain ./keys/device_ca_cert.pem \
  --device-cert-ca-private-key ./keys/device_ca_key.der \
  --manufacturer-cert ./keys/manufacturer_cert.pem \
  --rendezvous-info ./rendezvous-info.yml

How to use the Device Credential?

Before onboarding, the target device must install the Device Credential (device_credential_synquacer), which will be stored by U-Boot in the form of an EFI variable in this demo. Please note that in the later stage, a properly configured TFTP server will be required to transfer the credential to the target device and the process of setting up a TFTP server is not covered in this document. If you already have a TFTP server (/var/lib/tftpboot), you can follow the steps given below:

# docker cp fdo:/etc/fdo/stores/device_credentials/device_credential_synquacer /var/lib/tftpboot/

Extend the OV with the Owner's Certificate

[root@fedora fdo]# fdo-owner-tool extend-ownership-voucher \
    ./stores/owner_vouchers/ownership_voucher_synquacer \
    --current-owner-private-key ./keys/manufacturer_key.der \
    --new-owner-cert ./keys/owner_cert.pem

Convert the OV to COSE format

[root@fedora fdo]# fdo-owner-tool dump-ownership-voucher \
    ./stores/owner_vouchers/ownership_voucher_synquacer --outform cose > \
    ./stores/owner_vouchers/ownership_voucher_synquacer.cose
[root@fedora fdo]# GUID=$(fdo-owner-tool dump-ownership-voucher \
    ./stores/owner_vouchers/ownership_voucher_synquacer.cose \
    | awk '/Device GUID:/ {print $3}')
[root@fedora fdo]# mv ./stores/owner_vouchers/{ownership_voucher_synquacer.cose,$GUID}

Run FDO servers

[root@fedora fdo]# export LOG_LEVEL=info
[root@fedora fdo]# /usr/libexec/fdo/fdo-manufacturing-server &
[root@fedora fdo]# /usr/libexec/fdo/fdo-owner-onboarding-server &
[root@fedora fdo]# /usr/libexec/fdo/fdo-rendezvous-server &
[root@fedora fdo]# /usr/libexec/fdo/fdo-serviceinfo-api-server &

Remember to make sure the required ports (8080-8083/tcp) are open in your firewall.

Once package installation and configurations are completed, consider to commit the changes not to lose them after exiting the docker process.

$ docker commit fdo fedora:39

Setup the HTTP local server for Linux OS installation

This section explains how to automate the OS installation process and system configuration by providing a custom Fedora ISO image and kickstart file to the target device from a local HTTP server.

Download Fedora ISO image

mkdir -p ~/fdo-workspace/htdocs
cd ~/fdo-workspace/htdocs
wget https://download.fedoraproject.org/pub/alt/iot/39/IoT/aarch64/iso/Fedora-IoT-ostree-aarch64-39-20231103.1.iso

Create a custom ISO

Copy files from ISO to a working directory
mkdir /tmp/iso-orig /tmp/iso-cust
sudo mount -o loop ./Fedora-IoT-ostree-aarch64-39-20231103.1.iso /tmp/iso-orig
sudo rsync -av --exclude='*/install.img' /tmp/iso-orig/* /tmp/iso-cust
Write a kickstart file
touch ~/fdo-workspace/htdocs/synquacer.ks

Here's an example of a kickstart file that installs the minimal system with fdo-client on eMMC and creates a user named fdo with the password the same as its name.

 ~/fdo-workspace/htdocs/synquacer.ks
text

keyboard --vckeymap=us --xlayouts='us'
lang en_US.UTF-8
firewall --use-system-defaults
ostreesetup --osname="fedora-iot" --remote="fedora-iot" --url="file:///ostree/repo" --ref="fedora/stable/aarch64/iot" --nogpg

%post --erroronfail
rm -f /etc/ostree/remotes.d/fedora-iot.conf
ostree remote add --set=gpg-verify=true --set=gpgkeypath=/etc/pki/rpm-gpg/ --set=contenturl=mirrorlist=https://ostree.fedoraproject.org/iot/mirrorlist fedora-iot 'https://ostree.fedoraproject.org/iot'
cp /etc/skel/.bash* /root

# Generate the devcreds file from efivarfs
dd if=$(ls /sys/firmware/efi/efivars/DeviceCred-*) of=/boot/device-credentials bs=1 skip=4
cat <<EOF >"/boot/fdo-client-env"
DEVICE_CREDENTIAL="/boot/device-credentials"
EOF
# SELinux permissive for allowing fdo-client access anywhere
sed -i 's/^SELINUX=.*/SELINUX=permissive/' /etc/selinux/config
%end

firstboot --enable
skipx
ignoredisk --only-use=mmcblk0
bootloader --location=mbr --boot-drive=mmcblk0
autopart --type=plain
clearpart --all --initlabel --drives=mmcblk0
timezone Etc/UTC --utc
rootpw --lock
user --groups=wheel --name=fdo --plaintext --password=fdo
services --enabled=fdo-client-linuxapp
reboot
Edit the grub.cfg

Modify “inst.stage2” and “inst.ks” parameters in grub.cfg not to edit them at runtime during the OS installation.

mkdir /tmp/efiboot
sudo mount -o loop /tmp/iso-cust/images/efiboot.img /tmp/efiboot
awk -v KS="http://192.168.1.10/synquacer.ks" \
  -v STAGE2="https://dl.fedoraproject.org/pub/alt/iot/39/IoT/aarch64/os/" \
  '{sub(/^set default=.*/,"set default=\"0\"");\
    sub(/inst.stage2=[^[:space:]]*/,\
    "inst.stage2="STAGE2" inst.ks="KS" inst.noverifyssl noefi efi=runtime")} 1' \
  /tmp/efiboot/EFI/BOOT/grub.cfg \
  > tmp && sudo cp tmp /tmp/efiboot/EFI/BOOT/grub.cfg && rm tmp
sudo umount /tmp/efiboot && rm -r /tmp/efiboot

Despite its length the changes to grub.cfg made by the above commands are actually straightforward:

  • Selected ‘Fedora Installation’ as the default rather than Media checking

  • Added the following kernel args

inst.ks=http://192.168.1.10/synquacer.ks inst.stage2=https://dl.fedoraproject.org/pub/alt/iot/39/IoT/aarch64/os/ inst.noverifyssl noefi efi=runtime

The last three additions are meant to suppress warnings and errors during installation that may not be necessary or ideal.

Create new ISO
iso_label=$(blkid ./Fedora-IoT-ostree-aarch64-39-20231103.1.iso | \
  awk -F 'LABEL="' '{print $2}' | cut -d '"' -f 1)
sudo xorriso -as mkisofs -V $iso_label -r \
  -o ~/fdo-workspace/htdocs/Fedora-39-synquacer.iso \
  -J -joliet-long -cache-inodes -efi-boot-part --efi-boot-image \
  -e images/efiboot.img -no-emul-boot /tmp/iso-cust/
sudo implantisomd5 ~/fdo-workspace/htdocs/Fedora-39-synquacer.iso
sudo umount /tmp/iso-orig && sudo rm -r /tmp/iso-orig /tmp/iso-cust

Start HTTP server

docker run --rm -d -p 80:80/tcp -v ~/fdo-workspace/htdocs:/usr/local/apache2/htdocs httpd

On Synquacer

Device Onboarding Process

Power on the Synquacer and interrupt the autobooting by pressing any key to enter the U-Boot command line.

Install Device Credential

=> setenv autoload no
=> dhcp
=> setenv serverip 192.168.1.10
=> tftp $loadaddr device_credential_synquacer
=> setenv -e -nv -bs -rt -i ${loadaddr}:$filesize DeviceCred

Install OS by U-Boot UEFI HTTP

=> setenv httpserverip $serverip
=> efidebug boot add -u 5 fedora-inst http://${httpserverip}/Fedora-39-synquacer.iso
=> bootmenu

Select fedora-inst from the boot menu to start the installation process. The device will reboot upon completion.

Check FDO client logs

The FDO client will automatically initiate the device onboarding process during the first boot. You can view the outputs below.

systemctl status fdo-client-linuxapp -l --no-pager
fdo-client-logs.png