How the protocol works
The controller and the server do the same calculation. They use the HMAC-SHA256 function and shorten its result to six digits with the method known from one-time passwords (RFC 4226 and RFC 6238). Both implementations are checked against the same set of test vectors.
K_dev = HMAC-SHA256(K_master, "xAuth-v1-dev" || DevID || Epoch)
challenge = DT6( HMAC-SHA256(K_dev, 'C' || level || counter) ) controller
access code = DT6( HMAC-SHA256(K_dev, 'R' || level || challenge) ) server
- DevID is the unique ID of the controller, written as 16 hexadecimal characters, for example
0123456789ABCDEF. It is not secret. - K_master is the company master key. It exists only on the server.
- K_dev is the controller key. It is stored only in the controller’s NVRAM, and the library removes it from working memory after each calculation.
- Epoch is the version number of the key. It increases each time the key is replaced.
- The counter is increased and saved before each challenge is generated, so the input of the calculation never repeats.
- The level is part of both calculations, so a code issued for level 2 does not unlock level 3.
One HMAC calculation takes about 30 ms on a pCO5+ HS controller and about 33 ms on a c.pCO mini. For this reason, call the library in a task with an interval of at least 100 ms.
Access levels
| Level | Value | Typical use |
|---|
| User | 1 | end user, operator |
| Service | 2 | service technician |
| Manufacturer | 3 | manufacturer’s engineering department |
| Admin | 4 | production, full access |
You define the permissions of each level in your application with native STone profiles.
Default controller settings
| Rule | Default value | Parameter |
|---|
| Attempts for one challenge | 1 | none |
| Validity of a challenge | 300 s | ChallengeTtlS |
| Wrong codes before the lock | 10 | MaxFailures |
| Lock time | 900 s, doubled with each further lock, up to 24 h | LockTimeS, LockDoubling, LockMaxS |
| Session time after login | 3600 s | SessionTimeoutS |
If the controller is restarted during a lock, the lock starts again and lasts its full time. The controller does not trust the clock, so it cannot know how much time has passed. If the stored state is damaged, the controller stays locked.
At start-up the library disables the CAREL passepartout password. If the connection password or the binary password is not set in the controller, the library reports it on the SecWarnings output.
Loading the key into a controller
Each controller gets its key before first use, usually at the end of the production line or during commissioning. The key has 64 hexadecimal characters. You get it from the admin panel together with its check value, the KCV. After the key is loaded, the controller shows its own KCV. If the two values are the same, the controller holds the right key. The controller never shows the key itself.
| Method | Tool | Use |
|---|
| STone | Watch window | development, laboratory |
| Sparkly | Sparkly.exe parameters write | end of the production line, service laptop |
| pGD1 keypad | KeyEntryWidget | field work without a computer; only when the controller has no key yet or when an Admin is logged in |
By design, the key cannot be loaded over Modbus or BACnet. Do not expose the key-loading parameters to a BMS and do not add them to profiles.
Quick start
Controller (STone 2)
- Attach the
xAuth_v.0.2.0.stlib library to your project (Dependencies › Libs) and add the declaration USING Libs.xAuth;. - Call an instance of the
xAuth.Login block in a task with an interval of at least 100 ms. - Place the
Libs.xAuth_v0_2_0.xAuth.LoginWidget widget on an empty pGD1 terminal mask. If you want to load the key from the keypad, add KeyEntryWidget as well. - Load the controller key and compare the KCV values.
USING Libs.xAuth;
VAR_GLOBAL
XaAuth : xAuth.Login;
XaCfg : xAuth.Config := (MaxFailures := 10, LockTimeS := 900, LockDoubling := TRUE,
LockMaxS := 86400, ChallengeTtlS := 300, SessionTimeoutS := 3600);
END_VAR
(* cyclic task, interval >= 100 ms *)
xAuth.ProvFromParams(ProvKeyHex, ProvEpoch, ProvCommit, ProvResult, ProvKcv);
XaAuth(Cfg := XaCfg, Logout := logoutPulse);
IF XaAuth.State = xAuth.AuthState#Unlocked THEN
(* XaAuth.ActiveRole: 1 User, 2 Service, 3 Manufacturer, 4 Admin *)
END_IF;
You will find a complete example in the demo application.
Server (Docker)
Run the commands in the backend folder of the unpacked package. On Windows, docker compose build does not need BuildKit.
export XAUTH_ADMIN_TOKEN=$(python -c "import secrets;print(secrets.token_urlsafe(24))")
docker compose build
docker compose run --rm xauth xauth-admin key generate # once
docker compose run --rm xauth xauth-admin key export > master-key-backup.txt # keep away from the server
docker compose up -d
After start-up the admin panel is available at http://127.0.0.1:8000/admin and the technician panel at /tech. Your own service app can get access codes through the REST API:
POST /api/v1/codes
X-API-Key: <technician API key>
{"device_id": "0123456789ABCDEF", "role": 2, "challenge": "481773"}
| Area | Endpoints |
|---|
| Technicians | GET/POST /api/v1/technicians, change of level, revoking access, new API key |
| Controllers | GET/POST /api/v1/devices, /provision, /rotate, CSV import and export |
| Audit log | GET /api/v1/audit, CSV export |
| Access codes | POST /api/v1/codes (technician) |
The full API description, the commands of the xauth-admin tool and the backup and restore procedure are in the README.md file of the server package.
Requirements
- Controller: a CAREL controller programmed in STone 2, version 2.0.7 or later, with a pGD1 terminal. The library has been tested on c.pCO mini (firmware 3.3.005) and pCO5+ HS controllers.
- Controller settings: the connection password and the binary password set, encrypted download enabled, and a release build compiled without debug information.
- Server: Docker and a proxy server with HTTPS.