Security Key Service
The OPC UA Part 14 §8.4.2 GetSecurityKeys method, served by opcua-sks on port 4851. The test-only target for subscriber-side PubSub key clients.
The opcua-sks server (port 4851) is a regular OPC UA server
with one extra feature: it exposes the GetSecurityKeys
method from OPC UA Part 14 §8.4.2. PubSub subscribers use this
RPC to fetch the symmetric group keys they need to verify and
decrypt secured PubSub messages.
What it serves
| Property | Value |
|---|---|
| Endpoint | opc.tcp://localhost:4851/UA/TestServer |
| Policy | None |
| Mode | None |
| Auth | Anonymous |
| Object NodeId | ns=1;s=TestServer/SecurityKeyService |
| Method NodeId | ns=1;s=TestServer/SecurityKeyService/GetSecurityKeys |
Method signature
GetSecurityKeys(
securityGroupId: String,
startingTokenId: UInt32,
requestedKeyCount: UInt32,
) returns (
securityPolicyUri: String,
firstTokenId: UInt32,
keys: ByteString[],
timeToNextKey: Duration,
keyLifetime: Duration,
)
Default behaviour
For the configured group (OPCUA_SKS_GROUP_ID, default
test-group):
| Output | Default |
|---|---|
securityPolicyUri |
http://opcfoundation.org/UA/SecurityPolicy#PubSub-Aes256-CTR |
firstTokenId |
7 |
keys[0] |
68 bytes: 32 × 0x01 + 32 × 0x02 + 03 03 03 03 |
timeToNextKey |
300 000 ms (5 minutes) |
keyLifetime |
600 000 ms (10 minutes) |
requestedKeyCount is honored and capped to 8: the server
returns max(1, min(requestedKeyCount, 8)) byte strings. With
requestedKeyCount=3 you get three identical 68-byte ByteStrings;
with requestedKeyCount=100 you still only get 8. In this test
implementation the same key repeats — real SKS rotate them.
For unknown securityGroupId, the method returns Bad_NotFound.
Key layout — PubSub-Aes256-CTR
Each keys[i] ByteString is the concatenation of:
| Bytes | Field | Length |
|---|---|---|
0..31 |
HMAC-SHA256 signing key | 32 |
32..63 |
AES-256 encrypting key | 32 |
64..67 |
Key nonce | 4 |
Total: 68 bytes.
Subscriber-side code splits the ByteString and feeds each
fragment to the appropriate crypto primitive. The suite's
defaults are deliberately simple (32 × 0x01, etc.) so failing
crypto operations are easy to debug.
Environment-driven overrides
| Variable | Default | Effect |
|---|---|---|
OPCUA_ENABLE_SKS |
true on this service |
Enables the SKS builder |
OPCUA_SKS_GROUP_ID |
test-group |
Accepted group ID |
OPCUA_SKS_POLICY_URI |
…#PubSub-Aes256-CTR |
Returned policy URI |
OPCUA_SKS_TOKEN_ID |
7 |
Token ID of the current key |
OPCUA_SKS_SIGNING_KEY_HEX |
01 × 32 |
HMAC-SHA256 key (hex) |
OPCUA_SKS_ENCRYPTING_KEY_HEX |
02 × 32 |
AES-256 key (hex) |
OPCUA_SKS_KEY_NONCE_HEX |
03030303 |
Key nonce (hex) |
OPCUA_SKS_TIME_TO_NEXT_KEY_MS |
300000 |
Time-to-next-key value |
OPCUA_SKS_KEY_LIFETIME_MS |
600000 |
Key lifetime |
Test patterns
Happy path — fetch one key
call(
TestServer/SecurityKeyService,
TestServer/SecurityKeyService/GetSecurityKeys,
inputs = [
"test-group", # securityGroupId
0, # startingTokenId — ignored by this test impl
1, # requestedKeyCount
],
)
→ status = Good
→ outputs = [
securityPolicyUri = "…#PubSub-Aes256-CTR",
firstTokenId = 7,
keys = [<68 bytes>],
timeToNextKey = 300_000.0,
keyLifetime = 600_000.0,
]
Split the 68-byte ByteString:
| Bytes | Expected (defaults) |
|---|---|
0..31 |
All 0x01 |
32..63 |
All 0x02 |
64..67 |
03 03 03 03 |
Negative path — unknown group
call(..., inputs=["unknown-group", 0, 1])
→ status = Bad_NotFound
Multiple keys
call(..., inputs=["test-group", 0, 3])
→ keys = [<68 bytes>, <68 bytes>, <68 bytes>] (3 identical)
In a real SKS the keys differ — here they're the same because no real rotation exists.
Limits — test-only
This is not a reference SKS. Specifically:
- Single hardcoded group. Only
OPCUA_SKS_GROUP_IDis accepted. Unknown IDs returnBad_NotFound. - No caller authentication. Anyone reachable on 4851 can fetch keys. Real SKS enforce that the caller is part of the group.
- No rotation scheduling. Keys are static —
firstTokenIddoesn't advance. - No revocation. A compromised token is permanently compromised.
startingTokenIdis ignored. A real SKS returns keys starting at the requested token; this one always returns the current one.
If you're building a real SKS, this is fine to test the wire protocol against. Not fine to copy into production.
Integration with the PubSub publisher
The PubSub publisher (opcua-pubsub, UDP 14850) emits
unsecured UADP. The SKS provides keys for a hypothetical
secured publisher — currently not deployed in the suite. The
two services are complementary but not yet end-to-end joined.
A planned roadmap item is a secured publisher fed by the SKS's keys. See the project roadmap.
Where to read next
- PubSub publisher — the unsecured UADP source.
- Special-purpose servers — context for SKS + discovery + PubSub.