mirror of
https://github.com/kernelkit/infix.git
synced 2026-07-30 20:43:02 +02:00
Merge pull request #1379 from kernelkit/copilot/add-infix-keystore-documentation
Keystore refactor and documentation Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
This commit is contained in:
+2
-2
@@ -218,8 +218,8 @@
|
|||||||
"symmetric-key": [
|
"symmetric-key": [
|
||||||
{
|
{
|
||||||
"name": "wifi",
|
"name": "wifi",
|
||||||
"infix-keystore:symmetric-key": "infixinfix",
|
"cleartext-symmetric-key": "aW5maXhpbmZpeA==",
|
||||||
"infix-keystore:key-format": "infix-crypto-types:wifi-preshared-key-format"
|
"key-format": "infix-crypto-types:passphrase-key-format"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -107,8 +107,10 @@ To configure WiFi as a client, first store your WiFi password in the keystore:
|
|||||||
```
|
```
|
||||||
admin@infix:/> configure
|
admin@infix:/> configure
|
||||||
admin@infix:/config/> edit keystore symmetric-key mywifi
|
admin@infix:/config/> edit keystore symmetric-key mywifi
|
||||||
admin@infix:/config/keystore/…/mywifi/> set key-format wifi-preshared-key-format
|
admin@infix:/config/keystore/…/mywifi/> set key-format passphrase-key-format
|
||||||
admin@infix:/config/keystore/…/mywifi/> set symmetric-key YourWiFiPassword
|
admin@infix:/config/keystore/…/mywifi/> change cleartext-symmetric-key
|
||||||
|
Passphrase: ************
|
||||||
|
Retype passphrase: ************
|
||||||
admin@infix:/config/keystore/…/mywifi/> leave
|
admin@infix:/config/keystore/…/mywifi/> leave
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|||||||
@@ -1,23 +1,49 @@
|
|||||||
#!/bin/sh
|
#!/bin/sh
|
||||||
|
# Prompt for a secret with confirmation, then encode and output it.
|
||||||
|
#
|
||||||
|
# Default mode: hash with mkpasswd (for system passwords)
|
||||||
|
# askpass [OUTPUT]
|
||||||
|
#
|
||||||
|
# Base64 mode (-b): base64-encode (for keystore passphrases)
|
||||||
|
# askpass -b [OUTPUT]
|
||||||
|
#
|
||||||
|
# If OUTPUT is given, result is written to the file.
|
||||||
|
# If omitted, result is written to stdout.
|
||||||
# shellcheck disable=SC3045
|
# shellcheck disable=SC3045
|
||||||
|
|
||||||
|
LABEL="New password"
|
||||||
|
MODE=hash
|
||||||
|
if [ "$1" = "-b" ]; then
|
||||||
|
LABEL="Passphrase"
|
||||||
|
MODE=base64
|
||||||
|
shift
|
||||||
|
fi
|
||||||
OUTPUT=$1
|
OUTPUT=$1
|
||||||
|
|
||||||
read -r -s -p "New password: " password
|
read -r -s -p "$LABEL: " secret
|
||||||
>&2 echo
|
>&2 echo
|
||||||
read -r -s -p "Retype password: " password_again
|
read -r -s -p "Retype $LABEL: " secret_again
|
||||||
>&2 echo
|
>&2 echo
|
||||||
|
|
||||||
if [ "$password" != "$password_again" ]; then
|
if [ "$secret" != "$secret_again" ]; then
|
||||||
echo "Passwords do not match, try again."
|
echo "${LABEL}s do not match, try again."
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if [ -z "$OUTPUT" ]; then
|
if [ -z "$secret" ]; then
|
||||||
echo "$password"
|
echo "Empty $LABEL, try again."
|
||||||
exit 0
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ "$MODE" = "base64" ]; then
|
||||||
|
encoded=$(printf '%s' "$secret" | base64 -w 0)
|
||||||
|
else
|
||||||
|
encoded=$(printf '%s\n' "$secret" | mkpasswd -s)
|
||||||
fi
|
fi
|
||||||
|
|
||||||
umask 0177
|
umask 0177
|
||||||
echo "$password" | mkpasswd -s > "$OUTPUT"
|
if [ -z "$OUTPUT" ]; then
|
||||||
exit 0
|
echo "$encoded"
|
||||||
|
else
|
||||||
|
printf '%s' "$encoded" > "$OUTPUT"
|
||||||
|
fi
|
||||||
|
|||||||
+5
-2
@@ -3,7 +3,7 @@ Change Log
|
|||||||
|
|
||||||
All notable changes to the project are documented in this file.
|
All notable changes to the project are documented in this file.
|
||||||
|
|
||||||
[v26.01.0][UNRELEASED]
|
[v26.01.0][] - 2026-02-03
|
||||||
-------------------------
|
-------------------------
|
||||||
|
|
||||||
> [!IMPORTANT]
|
> [!IMPORTANT]
|
||||||
@@ -23,7 +23,7 @@ All notable changes to the project are documented in this file.
|
|||||||
|
|
||||||
Noteworthy changes and additions in this release are marked below in bold text.
|
Noteworthy changes and additions in this release are marked below in bold text.
|
||||||
|
|
||||||
- Upgrade Linux kernel to 6.18.8 (LTS)
|
- **Upgrade Linux kernel from 6.12.65 to 6.18.8 (LTS)**
|
||||||
- Upgrade Buildroot to 2025.02.10 (LTS)
|
- Upgrade Buildroot to 2025.02.10 (LTS)
|
||||||
- Upgrade libyang to 4.2.2
|
- Upgrade libyang to 4.2.2
|
||||||
- Upgrade sysrepo to 4.2.10
|
- Upgrade sysrepo to 4.2.10
|
||||||
@@ -53,6 +53,9 @@ Noteworthy changes and additions in this release are marked below in bold text.
|
|||||||
removed during upgrade (for the rest of the configuration to apply) and you
|
removed during upgrade (for the rest of the configuration to apply) and you
|
||||||
need to reconfigure them again. See the [WiFi][] documentation for details
|
need to reconfigure them again. See the [WiFi][] documentation for details
|
||||||
- Add support for **WireGuard VPN tunnels**.
|
- Add support for **WireGuard VPN tunnels**.
|
||||||
|
- Updated CLI change command to support `cleartext-symmetric-key` (type binary).
|
||||||
|
Used by both WireGuard and WiFi, with application-specific `key-format` for
|
||||||
|
keys and passphrases
|
||||||
- New default NACM privilege levels (user levels) in `factory-config`:
|
- New default NACM privilege levels (user levels) in `factory-config`:
|
||||||
`operator` (network & container manager) and `guest` (read-only). For
|
`operator` (network & container manager) and `guest` (read-only). For
|
||||||
details, see the updated system configuration documentation, as well as a
|
details, see the updated system configuration documentation, as well as a
|
||||||
|
|||||||
+193
@@ -0,0 +1,193 @@
|
|||||||
|
# Keystore
|
||||||
|
|
||||||
|
The Infix keystore is a centralized storage system for cryptographic keys
|
||||||
|
used throughout the system. It is based on the IETF standards [RFC 9641][1]
|
||||||
|
(Keystore) and [RFC 9640][2] (Cryptographic Types), with Infix extensions
|
||||||
|
for WiFi and WireGuard key formats.
|
||||||
|
|
||||||
|
## Overview
|
||||||
|
|
||||||
|
The keystore supports two types of cryptographic keys:
|
||||||
|
|
||||||
|
1. **Asymmetric Keys** — public/private key pairs used for:
|
||||||
|
- SSH host authentication (RSA keys)
|
||||||
|
- WireGuard VPN tunnels (X25519 keys)
|
||||||
|
|
||||||
|
2. **Symmetric Keys** — shared secrets used for:
|
||||||
|
- WiFi authentication (WPA2/WPA3 pre-shared keys)
|
||||||
|
- WireGuard VPN pre-shared keys
|
||||||
|
|
||||||
|
All keys are stored under the `ietf-keystore` configuration path and can be
|
||||||
|
managed via CLI, NETCONF, or RESTCONF.
|
||||||
|
|
||||||
|
### Supported Formats
|
||||||
|
|
||||||
|
| **Asymmetric Key Format** | **Use Case** | **Key Type** |
|
||||||
|
|----------------------------------------------------------|---------------|--------------|
|
||||||
|
| `rsa-private-key-format` / `ssh-public-key-format` | SSH host keys | RSA |
|
||||||
|
| `x25519-private-key-format` / `x25519-public-key-format` | WireGuard VPN | Curve25519 |
|
||||||
|
|
||||||
|
| **Symmetric Key Format** | **Use Case** |
|
||||||
|
|-----------------------------|-----------------------------------|
|
||||||
|
| `passphrase-key-format` | Human-readable passphrases (WiFi) |
|
||||||
|
| `octet-string-key-format` | Raw symmetric keys (WireGuard) |
|
||||||
|
|
||||||
|
## Asymmetric Keys
|
||||||
|
|
||||||
|
Asymmetric keys consist of a public/private key pair. The public key can be
|
||||||
|
shared freely, while the private key must be kept secure.
|
||||||
|
|
||||||
|
### SSH Host Keys
|
||||||
|
|
||||||
|
SSH host keys identify the system during SSH and NETCONF connections. The
|
||||||
|
default host key is automatically generated on first boot and stored in the
|
||||||
|
keystore with the name `genkey`.
|
||||||
|
|
||||||
|
See [SSH Management](management.md) for details on generating and importing
|
||||||
|
custom SSH host keys.
|
||||||
|
|
||||||
|
### WireGuard Keys
|
||||||
|
|
||||||
|
WireGuard uses X25519 elliptic curve cryptography for key exchange. Each
|
||||||
|
WireGuard interface requires a public/private key pair stored as an asymmetric
|
||||||
|
key in the keystore. Key pairs can be generated directly from the CLI:
|
||||||
|
|
||||||
|
<pre class="cli"><code>admin@example:/> <b>wireguard genkey</b>
|
||||||
|
Private: aMqBvZqkSP5JrqBvZqkSP5JrqBvZqkSP5JrqBvZqkSP=
|
||||||
|
Public: bN1CwZ1lTP6KsrCwZ1lTP6KsrCwZ1lTP6KsrCwZ1lTP=
|
||||||
|
</code></pre>
|
||||||
|
|
||||||
|
See [WireGuard VPN](vpn-wireguard.md) for key generation and configuration
|
||||||
|
examples.
|
||||||
|
|
||||||
|
## Symmetric Keys
|
||||||
|
|
||||||
|
Symmetric keys are shared secrets where the same key must be configured on
|
||||||
|
all systems that need to communicate.
|
||||||
|
|
||||||
|
### WiFi Pre-Shared Keys
|
||||||
|
|
||||||
|
WiFi networks secured with WPA2 or WPA3 use pre-shared keys stored as
|
||||||
|
symmetric keys in the keystore with `passphrase-key-format`. The
|
||||||
|
passphrase must be 8-63 printable ASCII characters.
|
||||||
|
|
||||||
|
Since symmetric keys are stored as binary (base64-encoded), the CLI
|
||||||
|
provides the `change` command to enter passphrases interactively:
|
||||||
|
|
||||||
|
<pre class="cli"><code>admin@example:/config/keystore/…/my-wifi-key/> <b>change cleartext-symmetric-key</b>
|
||||||
|
Passphrase: ************
|
||||||
|
Retype passphrase: ************
|
||||||
|
</code></pre>
|
||||||
|
|
||||||
|
See [WiFi](wifi.md) for complete configuration examples.
|
||||||
|
|
||||||
|
### WireGuard Pre-Shared Keys
|
||||||
|
|
||||||
|
WireGuard supports optional pre-shared keys (PSK) that add a layer of
|
||||||
|
symmetric encryption alongside Curve25519. PSKs use the standard IETF
|
||||||
|
`octet-string-key-format` (32 random bytes). This provides defense-in-depth
|
||||||
|
against future quantum computers that might break elliptic curve cryptography.
|
||||||
|
Note, however, that WireGuard’s authentication and initial key agreement
|
||||||
|
remain Curve25519-based, so PSKs only protect the session encryption,
|
||||||
|
not the handshake itself.
|
||||||
|
|
||||||
|
PSKs can be generated directly from the CLI:
|
||||||
|
|
||||||
|
<pre class="cli"><code>admin@example:/> <b>wireguard genpsk</b>
|
||||||
|
cO2DxZ2mUQ7LtsrDxZ2mUQ7LtsrDxZ2mUQ7LtsrDxZ2m=
|
||||||
|
</code></pre>
|
||||||
|
|
||||||
|
See [WireGuard VPN](vpn-wireguard.md) for PSK generation and usage examples.
|
||||||
|
|
||||||
|
## Viewing Keys
|
||||||
|
|
||||||
|
The `show keystore` command in admin-exec mode gives an overview of all
|
||||||
|
keys in the keystore. Passphrases (WiFi passwords) are decoded and shown
|
||||||
|
in cleartext, while binary keys (WireGuard PSKs) are shown as base64:
|
||||||
|
|
||||||
|
<pre class="cli"><code>admin@example:/> <b>show keystore</b>
|
||||||
|
────────────────────────────────────────────────────────────────────────
|
||||||
|
<span class="title">Symmetric Keys</span>
|
||||||
|
<span class="header">NAME FORMAT VALUE </span>
|
||||||
|
my-wifi-key passphrase MySecretPassword
|
||||||
|
wg-psk octet-string zYr83O4Ykj9i1gN+/aaosJxQx...
|
||||||
|
|
||||||
|
────────────────────────────────────────────────────────────────────────
|
||||||
|
<span class="title">Asymmetric Keys</span>
|
||||||
|
<span class="header">NAME TYPE PUBLIC KEY </span>
|
||||||
|
genkey rsa MIIBCgKCAQEAnj0YinjhYDgYbEGuh7...
|
||||||
|
wg-tunnel x25519 bN1CwZ1lTP6KsrCwZ1lTP6KsrCwZ1...
|
||||||
|
</code></pre>
|
||||||
|
|
||||||
|
To see the full (untruncated) details of a specific key, use the
|
||||||
|
`symmetric` or `asymmetric` qualifier with the key name:
|
||||||
|
|
||||||
|
<pre class="cli"><code>admin@example:/> <b>show keystore symmetric my-wifi-key</b>
|
||||||
|
name : my-wifi-key
|
||||||
|
format : passphrase
|
||||||
|
value : MySecretPassword
|
||||||
|
|
||||||
|
admin@example:/> <b>show keystore asymmetric genkey</b>
|
||||||
|
name : genkey
|
||||||
|
algorithm : rsa
|
||||||
|
public key format : ssh-public-key
|
||||||
|
public key : MIIBCgKCAQEAnj0YinjhY...full key...IDAQAB
|
||||||
|
</code></pre>
|
||||||
|
|
||||||
|
> [!NOTE]
|
||||||
|
> The `show keystore` command is protected by NACM. Only users in the
|
||||||
|
> `admin` group can view keystore data. Operator-level users will see a
|
||||||
|
> message indicating that no keystore data is available.
|
||||||
|
|
||||||
|
The full configuration-mode view (including private keys) is still
|
||||||
|
available via `configure` and then `show keystore`:
|
||||||
|
|
||||||
|
<pre class="cli"><code>admin@example:/config/> <b>show keystore</b>
|
||||||
|
</code></pre>
|
||||||
|
|
||||||
|
> [!WARNING]
|
||||||
|
> The configuration-mode `show keystore` displays private keys in
|
||||||
|
> cleartext. Be careful when viewing keys on shared screens or in
|
||||||
|
> logged sessions. The admin-exec `show keystore` command never
|
||||||
|
> displays private keys.
|
||||||
|
|
||||||
|
## Deleting Keys
|
||||||
|
|
||||||
|
<pre class="cli"><code>admin@example:/> <b>configure</b>
|
||||||
|
admin@example:/config/> <b>delete keystore asymmetric-key mykey</b>
|
||||||
|
admin@example:/config/> <b>leave</b>
|
||||||
|
</code></pre>
|
||||||
|
|
||||||
|
> [!CAUTION]
|
||||||
|
> Deleting a key that is referenced by a service (SSH, WireGuard, WiFi) will
|
||||||
|
> cause that service to fail. Verify the key is not in use before deletion.
|
||||||
|
|
||||||
|
## Security Considerations
|
||||||
|
|
||||||
|
The keystore is protected by NACM (Network Access Control Model) rules.
|
||||||
|
Only users in the `admin` group can view or modify cryptographic keys.
|
||||||
|
See [NACM](nacm.md) for details on access control.
|
||||||
|
|
||||||
|
Private keys are stored in cleartext in the configuration database.
|
||||||
|
Configuration files and backups containing the keystore should be treated
|
||||||
|
as sensitive and protected accordingly.
|
||||||
|
|
||||||
|
### Key Validation
|
||||||
|
|
||||||
|
Symmetric key values are stored as binary (base64-encoded). The system
|
||||||
|
validates them based on their declared format:
|
||||||
|
|
||||||
|
- `passphrase-key-format`: Used by WiFi, must decode to 8-63 ASCII characters
|
||||||
|
- `octet-string-key-format`: Used by Wireguard, must decode to exactly 32 bytes (256 bits)
|
||||||
|
|
||||||
|
## References
|
||||||
|
|
||||||
|
- [RFC 9641 - A YANG Data Model for a Keystore][1]
|
||||||
|
- [RFC 9640 - YANG Data Types and Groupings for Cryptography][2]
|
||||||
|
- [WiFi Documentation](wifi.md)
|
||||||
|
- [WireGuard VPN Documentation](vpn-wireguard.md)
|
||||||
|
- [SSH Management](management.md)
|
||||||
|
- [NACM Access Control](nacm.md)
|
||||||
|
|
||||||
|
[1]: https://datatracker.ietf.org/doc/html/rfc9641
|
||||||
|
[2]: https://datatracker.ietf.org/doc/html/rfc9640
|
||||||
@@ -47,6 +47,11 @@ RSA for now, thus the private key must be
|
|||||||
`ietf-crypto-types:rsa-private-key-format` and the public key
|
`ietf-crypto-types:rsa-private-key-format` and the public key
|
||||||
`ietf-crypto-types:ssh-public-key-format`
|
`ietf-crypto-types:ssh-public-key-format`
|
||||||
|
|
||||||
|
> [!TIP]
|
||||||
|
> For comprehensive information about the keystore, including key management,
|
||||||
|
> security considerations, and examples for different key types, see the
|
||||||
|
> [Keystore documentation](keystore.md).
|
||||||
|
|
||||||
### Use your own SSH hostkeys
|
### Use your own SSH hostkeys
|
||||||
|
|
||||||
Hostkeys can be generated with OpenSSL:
|
Hostkeys can be generated with OpenSSL:
|
||||||
|
|||||||
+17
-28
@@ -26,31 +26,25 @@ Key features of WireGuard:
|
|||||||
WireGuard uses public-key cryptography similar to SSH. Each WireGuard interface
|
WireGuard uses public-key cryptography similar to SSH. Each WireGuard interface
|
||||||
requires a private key, and each peer is identified by its public key.
|
requires a private key, and each peer is identified by its public key.
|
||||||
|
|
||||||
**Generate a WireGuard key pair using the `wg` command:**
|
**Import the key pair into the keystore:**
|
||||||
|
|
||||||
```bash
|
|
||||||
admin@example:~$ wg genkey | tee privatekey | wg pubkey > publickey
|
|
||||||
admin@example:~$ cat privatekey
|
|
||||||
aMqBvZqkSP5JrqBvZqkSP5JrqBvZqkSP5JrqBvZqkSP=
|
|
||||||
admin@example:~$ cat publickey
|
|
||||||
bN1CwZ1lTP6KsrCwZ1lTP6KsrCwZ1lTP6KsrCwZ1lTP=
|
|
||||||
```
|
|
||||||
|
|
||||||
This generates a private key, saves it to `privatekey`, derives the public key,
|
|
||||||
and saves it to `publickey`.
|
|
||||||
|
|
||||||
**Import the private key into the keystore:**
|
|
||||||
|
|
||||||
<pre class="cli"><code>admin@example:/> <b>configure</b>
|
<pre class="cli"><code>admin@example:/> <b>configure</b>
|
||||||
admin@example:/config/> <b>edit keystore asymmetric-key wg-site-a</b>
|
admin@example:/config/> <b>edit keystore asymmetric-key wg-site-a</b>
|
||||||
|
admin@example:/config/keystore/asymmetric-key/wg-site-a/> <b>do wireguard genkey</b>
|
||||||
|
Private: aMqBvZqkSP5JrqBvZqkSP5JrqBvZqkSP5JrqBvZqkSP=
|
||||||
|
Public: bN1CwZ1lTP6KsrCwZ1lTP6KsrCwZ1lTP6KsrCwZ1lTP=
|
||||||
admin@example:/config/keystore/asymmetric-key/wg-site-a/> <b>set public-key-format x25519-public-key-format</b>
|
admin@example:/config/keystore/asymmetric-key/wg-site-a/> <b>set public-key-format x25519-public-key-format</b>
|
||||||
admin@example:/config/keystore/asymmetric-key/wg-site-a/> <b>set private-key-format x25519-private-key-format</b>
|
admin@example:/config/keystore/asymmetric-key/wg-site-a/> <b>set private-key-format x25519-private-key-format</b>
|
||||||
admin@example:/config/keystore/asymmetric-key/wg-site-a/> <b>set public-key bN1CwZ1lTP6KsrCwZ1lTP6KsrCwZ1lTP6KsrCwZ1lTP=</b>
|
admin@example:/config/keystore/asymmetric-key/wg-site-a/> <b>set public-key bN1CwZ1lTP6KsrCwZ1lTP6KsrCwZ1lTP6KsrCwZ1lTP=</b>
|
||||||
admin@example:/config/keystore/asymmetric-key/wg-site-a/> <b>set private-key aMqBvZqkSP5JrqBvZqkSP5JrqBvZqkSP5JrqBvZqkSP=</b>
|
admin@example:/config/keystore/asymmetric-key/wg-site-a/> <b>set cleartext-private-key aMqBvZqkSP5JrqBvZqkSP5JrqBvZqkSP5JrqBvZqkSP=</b>
|
||||||
admin@example:/config/keystore/asymmetric-key/wg-site-a/> <b>leave</b>
|
admin@example:/config/keystore/asymmetric-key/wg-site-a/> <b>leave</b>
|
||||||
admin@example:/>
|
admin@example:/>
|
||||||
</code></pre>
|
</code></pre>
|
||||||
|
|
||||||
|
> [!TIP]
|
||||||
|
> The `do` prefix allows running admin-exec commands from configure context.
|
||||||
|
> Use the base for `wireguard genkey` when in admin-exec context.
|
||||||
|
|
||||||
**Import peer public keys into the truststore:**
|
**Import peer public keys into the truststore:**
|
||||||
|
|
||||||
<pre class="cli"><code>admin@example:/> <b>configure</b>
|
<pre class="cli"><code>admin@example:/> <b>configure</b>
|
||||||
@@ -63,8 +57,7 @@ admin@example:/>
|
|||||||
|
|
||||||
> [!IMPORTANT]
|
> [!IMPORTANT]
|
||||||
> Keep private keys secure! Never share your private key. Only exchange
|
> Keep private keys secure! Never share your private key. Only exchange
|
||||||
> public keys with peers. Delete the `privatekey` file after importing it
|
> public keys with peers.
|
||||||
> into the keystore.
|
|
||||||
|
|
||||||
## Point-to-Point Configuration
|
## Point-to-Point Configuration
|
||||||
|
|
||||||
@@ -394,20 +387,17 @@ recording traffic today would still need the PSK even if they break Curve25519
|
|||||||
later. However, peer authentication still relies on Curve25519, so PSKs don't
|
later. However, peer authentication still relies on Curve25519, so PSKs don't
|
||||||
provide complete post-quantum security.
|
provide complete post-quantum security.
|
||||||
|
|
||||||
**Generate a preshared key using `wg genpsk`:**
|
**Generate a preshared key:**
|
||||||
|
|
||||||
```bash
|
<pre class="cli"><code>admin@example:/config/> <b>do wireguard genpsk</b>
|
||||||
admin@example:~$ wg genpsk > preshared.key
|
|
||||||
admin@example:~$ cat preshared.key
|
|
||||||
cO2DxZ2mUQ7LtsrDxZ2mUQ7LtsrDxZ2mUQ7LtsrDxZ2m=
|
cO2DxZ2mUQ7LtsrDxZ2mUQ7LtsrDxZ2mUQ7LtsrDxZ2m=
|
||||||
```
|
</code></pre>
|
||||||
|
|
||||||
**Import the preshared key into the keystore:**
|
**Import the preshared key into the keystore:**
|
||||||
|
|
||||||
<pre class="cli"><code>admin@example:/> <b>configure</b>
|
<pre class="cli"><code>admin@example:/config/> <b>edit keystore symmetric-key wg-psk</b>
|
||||||
admin@example:/config/> <b>edit keystore symmetric-key wg-psk</b>
|
admin@example:/config/keystore/symmetric-key/wg-psk/> <b>set key-format octet-string-key-format</b>
|
||||||
admin@example:/config/keystore/symmetric-key/wg-psk/> <b>set key-format wireguard-symmetric-key-format</b>
|
admin@example:/config/keystore/symmetric-key/wg-psk/> <b>set cleartext-symmetric-key cO2DxZ2mUQ7LtsrDxZ2mUQ7LtsrDxZ2mUQ7LtsrDxZ2m=</b>
|
||||||
admin@example:/config/keystore/symmetric-key/wg-psk/> <b>set key cO2DxZ2mUQ7LtsrDxZ2mUQ7LtsrDxZ2mUQ7LtsrDxZ2m=</b>
|
|
||||||
admin@example:/config/keystore/symmetric-key/wg-psk/> <b>end</b>
|
admin@example:/config/keystore/symmetric-key/wg-psk/> <b>end</b>
|
||||||
admin@example:/config/interface/wg0/> <b>edit wireguard peers wg-peers peer remote</b>
|
admin@example:/config/interface/wg0/> <b>edit wireguard peers wg-peers peer remote</b>
|
||||||
admin@example:/config/interface/…/wg-peers/peer/remote/> <b>set preshared-key wg-psk</b>
|
admin@example:/config/interface/…/wg-peers/peer/remote/> <b>set preshared-key wg-psk</b>
|
||||||
@@ -420,5 +410,4 @@ on both sides.
|
|||||||
|
|
||||||
> [!IMPORTANT]
|
> [!IMPORTANT]
|
||||||
> Preshared keys must be kept secret and exchanged through a secure channel,
|
> Preshared keys must be kept secret and exchanged through a secure channel,
|
||||||
> just like passwords. Delete the `preshared.key` file after importing it
|
> just like passwords.
|
||||||
> into both peer keystores.
|
|
||||||
|
|||||||
+45
-14
@@ -241,6 +241,23 @@ In the CLI, signal strength is reported as: excellent, good, fair or bad.
|
|||||||
For precise signal strength values in dBm, use NETCONF or RESTCONF to access
|
For precise signal strength values in dBm, use NETCONF or RESTCONF to access
|
||||||
the `signal-strength` leaf in the operational datastore.
|
the `signal-strength` leaf in the operational datastore.
|
||||||
|
|
||||||
|
## Passphrase Requirements
|
||||||
|
|
||||||
|
To ensure your connection is secure and compatible with all network
|
||||||
|
hardware, your passphrase must meet the following criteria:
|
||||||
|
|
||||||
|
- Length: Between 8 and 63 characters
|
||||||
|
- Characters: Use only standard English keyboard characters
|
||||||
|
- Allowed: Letters (A-Z, a-z), numbers (0-9), and common symbols (e.g., ! @ # $ % ^ & * ( ) _ + - = [ ] { } | ; : ' " , . < > / ? ~)
|
||||||
|
- Spaces: Spaces are allowed, but not at the very beginning or very end of the passphrase
|
||||||
|
- Prohibited: Emojis, accented characters (like á or ñ), and special "control" characters
|
||||||
|
|
||||||
|
> [!TIP] Why the limit?
|
||||||
|
> Standard WiFi security (WPA2/WPA3) requires a minimum of 8 characters to
|
||||||
|
> prevent "brute-force" hacking. The character limit ensures your password
|
||||||
|
> works on older routers and various operating systems.
|
||||||
|
> Tips for password strength, see [XKCD #936](https://xkcd.com/936/).
|
||||||
|
|
||||||
## Station Mode (Client)
|
## Station Mode (Client)
|
||||||
|
|
||||||
Station mode connects to an existing Wi-Fi network. Before configuring station
|
Station mode connects to an existing Wi-Fi network. Before configuring station
|
||||||
@@ -253,11 +270,16 @@ Create a keystore entry for your WiFi password (8-63 characters):
|
|||||||
|
|
||||||
<pre class="cli"><code>admin@example:/> <b>configure</b>
|
<pre class="cli"><code>admin@example:/> <b>configure</b>
|
||||||
admin@example:/config/> <b>edit keystore symmetric-key my-wifi-key</b>
|
admin@example:/config/> <b>edit keystore symmetric-key my-wifi-key</b>
|
||||||
admin@example:/config/keystore/…/my-wifi-key/> <b>set key-format wifi-preshared-key-format</b>
|
admin@example:/config/keystore/…/my-wifi-key/> <b>set key-format passphrase-key-format</b>
|
||||||
admin@example:/config/keystore/…/my-wifi-key/> <b>set symmetric-key MyPassword123</b>
|
admin@example:/config/keystore/…/my-wifi-key/> <b>change cleartext-symmetric-key</b>
|
||||||
|
Passphrase: ************
|
||||||
|
Retype passphrase: ************
|
||||||
admin@example:/config/keystore/…/my-wifi-key/> <b>leave</b>
|
admin@example:/config/keystore/…/my-wifi-key/> <b>leave</b>
|
||||||
</code></pre>
|
</code></pre>
|
||||||
|
|
||||||
|
The `change` command prompts for the passphrase interactively and
|
||||||
|
handles the base64 encoding required by the keystore automatically.
|
||||||
|
|
||||||
### Step 2: Connect to Network
|
### Step 2: Connect to Network
|
||||||
|
|
||||||
Configure station mode with the SSID and password to connect:
|
Configure station mode with the SSID and password to connect:
|
||||||
@@ -276,8 +298,9 @@ name : wifi0
|
|||||||
type : wifi
|
type : wifi
|
||||||
operational status : up
|
operational status : up
|
||||||
physical address : f0:09:0d:36:5f:86
|
physical address : f0:09:0d:36:5f:86
|
||||||
SSID : MyHomeNetwork
|
mode : station
|
||||||
Signal : excellent
|
ssid : MyHomeNetwork
|
||||||
|
signal : -52 dBm (good)
|
||||||
</code></pre>
|
</code></pre>
|
||||||
|
|
||||||
**Station configuration parameters:**
|
**Station configuration parameters:**
|
||||||
@@ -310,8 +333,10 @@ create a keystore entry for your WiFi password and configure the AP interface:
|
|||||||
|
|
||||||
<pre class="cli"><code>admin@example:/> <b>configure</b>
|
<pre class="cli"><code>admin@example:/> <b>configure</b>
|
||||||
admin@example:/config/> <b>edit keystore symmetric-key my-wifi-secret</b>
|
admin@example:/config/> <b>edit keystore symmetric-key my-wifi-secret</b>
|
||||||
admin@example:/config/keystore/…/my-wifi-secret/> <b>set key-format wifi-preshared-key-format</b>
|
admin@example:/config/keystore/…/my-wifi-secret/> <b>set key-format passphrase-key-format</b>
|
||||||
admin@example:/config/keystore/…/my-wifi-secret/> <b>set symmetric-key MySecurePassword123</b>
|
admin@example:/config/keystore/…/my-wifi-secret/> <b>change cleartext-symmetric-key</b>
|
||||||
|
Passphrase: ************
|
||||||
|
Retype passphrase: ************
|
||||||
admin@example:/config/keystore/…/my-wifi-secret/> <b>end</b>
|
admin@example:/config/keystore/…/my-wifi-secret/> <b>end</b>
|
||||||
</code></pre>
|
</code></pre>
|
||||||
|
|
||||||
@@ -371,14 +396,20 @@ admin@example:/config/hardware/component/radio0/wifi-radio/> <b>leave</b>
|
|||||||
|
|
||||||
<pre class="cli"><code>admin@example:/> <b>configure</b>
|
<pre class="cli"><code>admin@example:/> <b>configure</b>
|
||||||
admin@example:/config/> <b>edit keystore symmetric-key main-secret</b>
|
admin@example:/config/> <b>edit keystore symmetric-key main-secret</b>
|
||||||
admin@example:/config/keystore/…/main-secret/> <b>set key-format wifi-preshared-key-format</b>
|
admin@example:/config/keystore/…/main-secret/> <b>set key-format passphrase-key-format</b>
|
||||||
admin@example:/config/keystore/…/main-secret/> <b>set symmetric-key MyMainPassword</b>
|
admin@example:/config/keystore/…/main-secret/> <b>change cleartext-symmetric-key</b>
|
||||||
|
Passphrase: ************
|
||||||
|
Retype passphrase: ************
|
||||||
admin@example:/config/> <b>edit keystore symmetric-key guest-secret</b>
|
admin@example:/config/> <b>edit keystore symmetric-key guest-secret</b>
|
||||||
admin@example:/config/keystore/…/guest-secret/> <b>set key-format wifi-preshared-key-format</b>
|
admin@example:/config/keystore/…/guest-secret/> <b>set key-format passphrase-key-format</b>
|
||||||
admin@example:/config/keystore/…/guest-secret/> <b>set symmetric-key GuestPassword123</b>
|
admin@example:/config/keystore/…/guest-secret/> <b>change cleartext-symmetric-key</b>
|
||||||
|
Passphrase: ************
|
||||||
|
Retype passphrase: ************
|
||||||
admin@example:/config/> <b>edit keystore symmetric-key iot-secret</b>
|
admin@example:/config/> <b>edit keystore symmetric-key iot-secret</b>
|
||||||
admin@example:/config/keystore/…/iot-secret/> <b>set key-format wifi-preshared-key-format</b>
|
admin@example:/config/keystore/…/iot-secret/> <b>set key-format passphrase-key-format</b>
|
||||||
admin@example:/config/keystore/…/iot-secret/> <b>set symmetric-key IoTDevices2025</b>
|
admin@example:/config/keystore/…/iot-secret/> <b>change cleartext-symmetric-key</b>
|
||||||
|
Passphrase: ************
|
||||||
|
Retype passphrase: ************
|
||||||
admin@example:/config/keystore/…/iot-secret/> <b>leave</b>
|
admin@example:/config/keystore/…/iot-secret/> <b>leave</b>
|
||||||
</code></pre>
|
</code></pre>
|
||||||
|
|
||||||
@@ -466,8 +497,8 @@ If issues arise, try the following troubleshooting steps:
|
|||||||
|
|
||||||
1. **Verify signal strength**: Check that the target network shows
|
1. **Verify signal strength**: Check that the target network shows
|
||||||
"good" or "excellent" signal in scan results
|
"good" or "excellent" signal in scan results
|
||||||
2. **Check credentials**: Verify the preshared key in the keystore
|
2. **Check credentials**: Use `show keystore symmetric <name>` to verify
|
||||||
matches the network password
|
the passphrase matches the network password
|
||||||
3. **Review logs**: Check system logs with `show log` for Wi-Fi related
|
3. **Review logs**: Check system logs with `show log` for Wi-Fi related
|
||||||
errors
|
errors
|
||||||
4. **Regulatory compliance**: Ensure the country-code on the radio
|
4. **Regulatory compliance**: Ensure the country-code on the radio
|
||||||
|
|||||||
@@ -53,6 +53,7 @@ nav:
|
|||||||
- Access Control (NACM): nacm.md
|
- Access Control (NACM): nacm.md
|
||||||
- Hardware Info & Status: hardware.md
|
- Hardware Info & Status: hardware.md
|
||||||
- Management: management.md
|
- Management: management.md
|
||||||
|
- Keystore: keystore.md
|
||||||
- Syslog Support: syslog.md
|
- Syslog Support: syslog.md
|
||||||
- Support Data: support.md
|
- Support Data: support.md
|
||||||
- Upgrade: upgrade.md
|
- Upgrade: upgrade.md
|
||||||
|
|||||||
@@ -1,3 +1,3 @@
|
|||||||
# Locally calculated
|
# Locally calculated
|
||||||
sha256 9d9d33b873917ca5d0bdcc47a36d2fd385971ab0c045d1472fcadf95ee5bcf5b LICENCE
|
sha256 9d9d33b873917ca5d0bdcc47a36d2fd385971ab0c045d1472fcadf95ee5bcf5b LICENCE
|
||||||
sha256 6e098390be2a78a56cf94eecb9d28b11c1791dd2c364e59602bbd664b508fd57 klish-plugin-sysrepo-a4b1fae697b51614dc75989e8f4fc8d277689d16-git4.tar.gz
|
sha256 7bfdaef838ee8bd3995140c40144abe79ff1c6391c7b48445e6728cd4e7e56a8 klish-plugin-sysrepo-2f14503c7ea6eb24c8adaf0cf2cf7a511114b09e-git4.tar.gz
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
#
|
#
|
||||||
################################################################################
|
################################################################################
|
||||||
|
|
||||||
KLISH_PLUGIN_SYSREPO_VERSION = a4b1fae697b51614dc75989e8f4fc8d277689d16
|
KLISH_PLUGIN_SYSREPO_VERSION = 2f14503c7ea6eb24c8adaf0cf2cf7a511114b09e
|
||||||
KLISH_PLUGIN_SYSREPO_SITE = https://github.com/kernelkit/klish-plugin-sysrepo.git
|
KLISH_PLUGIN_SYSREPO_SITE = https://github.com/kernelkit/klish-plugin-sysrepo.git
|
||||||
#KLISH_PLUGIN_SYSREPO_VERSION = cdd3eb51a7f7ee0ed5bd925fa636061d3b1b85fb
|
#KLISH_PLUGIN_SYSREPO_VERSION = cdd3eb51a7f7ee0ed5bd925fa636061d3b1b85fb
|
||||||
#KLISH_PLUGIN_SYSREPO_SITE = https://src.libcode.org/pkun/klish-plugin-sysrepo.git
|
#KLISH_PLUGIN_SYSREPO_SITE = https://src.libcode.org/pkun/klish-plugin-sysrepo.git
|
||||||
|
|||||||
@@ -190,7 +190,7 @@ def interface(args: List[str]) -> None:
|
|||||||
elif len(args) == 1:
|
elif len(args) == 1:
|
||||||
iface = args[0]
|
iface = args[0]
|
||||||
if is_valid_interface_name(iface):
|
if is_valid_interface_name(iface):
|
||||||
cli_pretty(data, f"show-interfaces", "-n", iface)
|
cli_pretty(data, "show-interfaces", "-n", iface)
|
||||||
else:
|
else:
|
||||||
print(f"Invalid interface name: {iface}")
|
print(f"Invalid interface name: {iface}")
|
||||||
else:
|
else:
|
||||||
@@ -659,6 +659,25 @@ def nacm(args: List[str]) -> None:
|
|||||||
print(f"Unknown NACM subcommand: {subcommand}")
|
print(f"Unknown NACM subcommand: {subcommand}")
|
||||||
|
|
||||||
|
|
||||||
|
def keystore(args: List[str]) -> None:
|
||||||
|
data = get_json("/ietf-keystore:keystore", "running", quiet=True)
|
||||||
|
if not data:
|
||||||
|
user = os.environ.get('USER', 'unknown')
|
||||||
|
print(f'No keystore data available (check NACM permissions for "{user}").')
|
||||||
|
return
|
||||||
|
|
||||||
|
if RAW_OUTPUT:
|
||||||
|
print(json.dumps(data, indent=2))
|
||||||
|
return
|
||||||
|
|
||||||
|
if len(args) == 0 or not args[0]:
|
||||||
|
cli_pretty(data, "show-keystore")
|
||||||
|
elif len(args) >= 2 and args[0] in ("symmetric", "asymmetric"):
|
||||||
|
cli_pretty(data, "show-keystore", "-t", args[0], "-n", args[1])
|
||||||
|
else:
|
||||||
|
print("Usage: show keystore [symmetric <name> | asymmetric <name>]")
|
||||||
|
|
||||||
|
|
||||||
def execute_command(command: str, args: List[str]):
|
def execute_command(command: str, args: List[str]):
|
||||||
command_mapping = {
|
command_mapping = {
|
||||||
'bfd': bfd,
|
'bfd': bfd,
|
||||||
@@ -667,6 +686,7 @@ def execute_command(command: str, args: List[str]):
|
|||||||
'dhcp': dhcp,
|
'dhcp': dhcp,
|
||||||
'hardware': hardware,
|
'hardware': hardware,
|
||||||
'interface': interface,
|
'interface': interface,
|
||||||
|
'keystore': keystore,
|
||||||
'lldp': lldp,
|
'lldp': lldp,
|
||||||
'nacm': nacm,
|
'nacm': nacm,
|
||||||
'ntp': ntp,
|
'ntp': ntp,
|
||||||
|
|||||||
@@ -1,5 +1,13 @@
|
|||||||
#!/bin/sh
|
#!/bin/sh
|
||||||
# Rename cleartext-key to symmetric-key
|
# Migrate symmetric keys from v1.6 (v25.11) to IETF standard format.
|
||||||
|
#
|
||||||
|
# v1.6 had WiFi keys stored as:
|
||||||
|
# - infix-keystore:cleartext-key (type string, plaintext)
|
||||||
|
# - infix-keystore:key-format wifi-preshared-key-format
|
||||||
|
#
|
||||||
|
# v1.7 uses the IETF standard leaf and updated format names:
|
||||||
|
# - cleartext-symmetric-key (type binary, base64-encoded)
|
||||||
|
# - key-format passphrase-key-format
|
||||||
|
|
||||||
file=$1
|
file=$1
|
||||||
temp=${file}.tmp
|
temp=${file}.tmp
|
||||||
@@ -7,12 +15,17 @@ temp=${file}.tmp
|
|||||||
jq '
|
jq '
|
||||||
if .["ietf-keystore:keystore"]?."symmetric-keys"?."symmetric-key" then
|
if .["ietf-keystore:keystore"]?."symmetric-keys"?."symmetric-key" then
|
||||||
.["ietf-keystore:keystore"]."symmetric-keys"."symmetric-key" |= map(
|
.["ietf-keystore:keystore"]."symmetric-keys"."symmetric-key" |= map(
|
||||||
|
if ."infix-keystore:key-format" then
|
||||||
|
del(."infix-keystore:key-format") |
|
||||||
|
. + { "key-format": "infix-crypto-types:passphrase-key-format" }
|
||||||
|
else
|
||||||
|
.
|
||||||
|
end |
|
||||||
|
|
||||||
if ."infix-keystore:cleartext-key" then
|
if ."infix-keystore:cleartext-key" then
|
||||||
# Rename cleartext-key to symmetric-key
|
."infix-keystore:cleartext-key" as $val |
|
||||||
."infix-keystore:cleartext-key" as $key_value |
|
del(."infix-keystore:cleartext-key") |
|
||||||
del(."infix-keystore:cleartext-key") | . + {
|
. + { "cleartext-symmetric-key": ($val | @base64) }
|
||||||
"infix-keystore:symmetric-key": $key_value
|
|
||||||
}
|
|
||||||
else
|
else
|
||||||
.
|
.
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -12,6 +12,7 @@
|
|||||||
#include "core.h"
|
#include "core.h"
|
||||||
#include "interfaces.h"
|
#include "interfaces.h"
|
||||||
#include "dagger.h"
|
#include "dagger.h"
|
||||||
|
#include "base64.h"
|
||||||
|
|
||||||
#define XPATH_BASE_ "/ietf-hardware:hardware"
|
#define XPATH_BASE_ "/ietf-hardware:hardware"
|
||||||
#define HOSTAPD_CONF "/etc/hostapd-%s.conf"
|
#define HOSTAPD_CONF "/etc/hostapd-%s.conf"
|
||||||
@@ -235,8 +236,9 @@ static int wifi_find_radio_aps(struct lyd_node *cifs, const char *radio_name,
|
|||||||
/* Helper: Write SSID and security configuration (shared between primary and BSS) */
|
/* Helper: Write SSID and security configuration (shared between primary and BSS) */
|
||||||
static void wifi_gen_ssid_config(FILE *hostapd, struct lyd_node *cif, struct lyd_node *config, bool is_bss)
|
static void wifi_gen_ssid_config(FILE *hostapd, struct lyd_node *cif, struct lyd_node *config, bool is_bss)
|
||||||
{
|
{
|
||||||
const char *ssid, *hidden, *security_mode, *secret_name, *secret;
|
const char *ssid, *hidden, *security_mode, *secret_name;
|
||||||
struct lyd_node *wifi, *ap, *security, *secret_node;
|
struct lyd_node *wifi, *ap, *security, *secret_node;
|
||||||
|
unsigned char *secret = NULL;
|
||||||
const char *ifname;
|
const char *ifname;
|
||||||
char bssid[18];
|
char bssid[18];
|
||||||
|
|
||||||
@@ -282,15 +284,19 @@ static void wifi_gen_ssid_config(FILE *hostapd, struct lyd_node *cif, struct lyd
|
|||||||
security_mode = "open";
|
security_mode = "open";
|
||||||
|
|
||||||
/* Get secret from keystore if needed */
|
/* Get secret from keystore if needed */
|
||||||
secret = NULL;
|
|
||||||
if (strcmp(security_mode, "open") != 0) {
|
if (strcmp(security_mode, "open") != 0) {
|
||||||
secret_name = lydx_get_cattr(security, "secret");
|
secret_name = lydx_get_cattr(security, "secret");
|
||||||
if (secret_name) {
|
if (secret_name) {
|
||||||
|
const char *b64;
|
||||||
|
|
||||||
secret_node = lydx_get_xpathf(config,
|
secret_node = lydx_get_xpathf(config,
|
||||||
"/keystore/symmetric-keys/symmetric-key[name='%s']/symmetric-key",
|
"/keystore/symmetric-keys/symmetric-key[name='%s']/cleartext-symmetric-key",
|
||||||
secret_name);
|
secret_name);
|
||||||
if (secret_node)
|
if (secret_node) {
|
||||||
secret = lyd_get_value(secret_node);
|
b64 = lyd_get_value(secret_node);
|
||||||
|
if (b64)
|
||||||
|
secret = base64_decode((const unsigned char *)b64, strlen(b64), NULL);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -329,6 +335,8 @@ static void wifi_gen_ssid_config(FILE *hostapd, struct lyd_node *cif, struct lyd
|
|||||||
/* ieee80211w=1: MFP capable but optional, for WPA2 client compatibility */
|
/* ieee80211w=1: MFP capable but optional, for WPA2 client compatibility */
|
||||||
fprintf(hostapd, "ieee80211w=1\n");
|
fprintf(hostapd, "ieee80211w=1\n");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
free(secret);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Helper: Write radio-specific configuration */
|
/* Helper: Write radio-specific configuration */
|
||||||
|
|||||||
+76
-8
@@ -7,14 +7,76 @@
|
|||||||
* configuration (hostapd) is handled by hardware.c.
|
* configuration (hostapd) is handled by hardware.c.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
#include <ctype.h>
|
||||||
|
|
||||||
#include <srx/lyx.h>
|
#include <srx/lyx.h>
|
||||||
#include <srx/srx_val.h>
|
#include <srx/srx_val.h>
|
||||||
|
|
||||||
#include "interfaces.h"
|
#include "interfaces.h"
|
||||||
|
#include "base64.h"
|
||||||
|
|
||||||
#define WPA_SUPPLICANT_CONF "/etc/wpa_supplicant-%s.conf"
|
#define WPA_SUPPLICANT_CONF "/etc/wpa_supplicant-%s.conf"
|
||||||
|
|
||||||
|
|
||||||
|
int wifi_validate_secret(sr_session_ctx_t *session, struct lyd_node *cif)
|
||||||
|
{
|
||||||
|
struct lyd_node *wifi, *station, *security, *secret_node;
|
||||||
|
const char *ifname, *secret_name, *security_mode, *b64;
|
||||||
|
unsigned char *decoded;
|
||||||
|
size_t len;
|
||||||
|
|
||||||
|
ifname = lydx_get_cattr(cif, "name");
|
||||||
|
wifi = lydx_get_child(cif, "wifi");
|
||||||
|
if (!wifi)
|
||||||
|
return SR_ERR_OK;
|
||||||
|
|
||||||
|
station = lydx_get_child(wifi, "station");
|
||||||
|
if (!station)
|
||||||
|
return SR_ERR_OK;
|
||||||
|
|
||||||
|
security = lydx_get_child(station, "security");
|
||||||
|
security_mode = lydx_get_cattr(security, "mode");
|
||||||
|
secret_name = lydx_get_cattr(security, "secret");
|
||||||
|
|
||||||
|
if (!secret_name || !strcmp(security_mode, "disabled"))
|
||||||
|
return SR_ERR_OK;
|
||||||
|
|
||||||
|
secret_node = lydx_get_xpathf(cif,
|
||||||
|
"../../keystore/symmetric-keys/symmetric-key[name='%s']",
|
||||||
|
secret_name);
|
||||||
|
b64 = lydx_get_cattr(secret_node, "cleartext-symmetric-key");
|
||||||
|
if (!b64 || !*b64)
|
||||||
|
return SR_ERR_OK;
|
||||||
|
|
||||||
|
decoded = base64_decode((const unsigned char *)b64, strlen(b64), &len);
|
||||||
|
if (!decoded)
|
||||||
|
return SR_ERR_OK;
|
||||||
|
|
||||||
|
if (len < 8 || len > 63) {
|
||||||
|
if (session)
|
||||||
|
sr_session_set_error_message(session,
|
||||||
|
"%s: WiFi passphrase must be 8-63 characters, got %zu",
|
||||||
|
ifname, len);
|
||||||
|
free(decoded);
|
||||||
|
return SR_ERR_VALIDATION_FAILED;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (size_t i = 0; i < len; i++) {
|
||||||
|
if (!isprint((unsigned char)decoded[i])) {
|
||||||
|
if (session)
|
||||||
|
sr_session_set_error_message(session,
|
||||||
|
"%s: WiFi passphrase contains non-printable "
|
||||||
|
"character at position %zu",
|
||||||
|
ifname, i + 1);
|
||||||
|
free(decoded);
|
||||||
|
return SR_ERR_VALIDATION_FAILED;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
free(decoded);
|
||||||
|
return SR_ERR_OK;
|
||||||
|
}
|
||||||
|
|
||||||
wifi_mode_t wifi_get_mode(struct lyd_node *iface)
|
wifi_mode_t wifi_get_mode(struct lyd_node *iface)
|
||||||
{
|
{
|
||||||
struct lyd_node *ap, *wifi;
|
struct lyd_node *ap, *wifi;
|
||||||
@@ -59,8 +121,9 @@ int wifi_mode_changed(struct lyd_node *wifi)
|
|||||||
*/
|
*/
|
||||||
int wifi_gen_station(struct lyd_node *cif)
|
int wifi_gen_station(struct lyd_node *cif)
|
||||||
{
|
{
|
||||||
const char *ifname, *ssid, *secret_name, *secret, *security_mode, *radio;
|
const char *ifname, *ssid, *secret_name, *security_mode, *radio;
|
||||||
struct lyd_node *security, *secret_node, *radio_node, *station, *wifi;
|
struct lyd_node *security, *secret_node, *radio_node, *station, *wifi;
|
||||||
|
unsigned char *secret = NULL;
|
||||||
FILE *wpa_supplicant = NULL;
|
FILE *wpa_supplicant = NULL;
|
||||||
char *security_str = NULL;
|
char *security_str = NULL;
|
||||||
const char *country;
|
const char *country;
|
||||||
@@ -91,12 +154,14 @@ int wifi_gen_station(struct lyd_node *cif)
|
|||||||
country = lydx_get_cattr(radio_node, "country-code");
|
country = lydx_get_cattr(radio_node, "country-code");
|
||||||
|
|
||||||
if (secret_name && strcmp(security_mode, "disabled") != 0) {
|
if (secret_name && strcmp(security_mode, "disabled") != 0) {
|
||||||
|
const char *b64;
|
||||||
|
|
||||||
secret_node = lydx_get_xpathf(cif,
|
secret_node = lydx_get_xpathf(cif,
|
||||||
"../../keystore/symmetric-keys/symmetric-key[name='%s']",
|
"../../keystore/symmetric-keys/symmetric-key[name='%s']",
|
||||||
secret_name);
|
secret_name);
|
||||||
secret = lydx_get_cattr(secret_node, "symmetric-key");
|
b64 = lydx_get_cattr(secret_node, "cleartext-symmetric-key");
|
||||||
} else {
|
if (b64)
|
||||||
secret = NULL;
|
secret = base64_decode((const unsigned char *)b64, strlen(b64), NULL);
|
||||||
}
|
}
|
||||||
|
|
||||||
oldmask = umask(0077);
|
oldmask = umask(0077);
|
||||||
@@ -121,11 +186,13 @@ int wifi_gen_station(struct lyd_node *cif)
|
|||||||
/* If SSID is present, create network block. Otherwise, scan-only mode */
|
/* If SSID is present, create network block. Otherwise, scan-only mode */
|
||||||
if (ssid) {
|
if (ssid) {
|
||||||
/* Station mode with network configured */
|
/* Station mode with network configured */
|
||||||
if (!strcmp(security_mode, "disabled")) {
|
if (!strcmp(security_mode, "disabled"))
|
||||||
asprintf(&security_str, "key_mgmt=NONE");
|
asprintf(&security_str, "key_mgmt=NONE");
|
||||||
} else if (secret) {
|
else if (secret)
|
||||||
asprintf(&security_str, "key_mgmt=SAE WPA-PSK\npsk=\"%s\"", secret);
|
asprintf(&security_str,
|
||||||
}
|
"key_mgmt=SAE WPA-PSK\n"
|
||||||
|
" psk=\"%s\"", secret);
|
||||||
|
|
||||||
fprintf(wpa_supplicant,
|
fprintf(wpa_supplicant,
|
||||||
"network={\n"
|
"network={\n"
|
||||||
" bgscan=\"simple: 30:-45:300\"\n"
|
" bgscan=\"simple: 30:-45:300\"\n"
|
||||||
@@ -152,6 +219,7 @@ int wifi_gen_station(struct lyd_node *cif)
|
|||||||
}
|
}
|
||||||
|
|
||||||
out:
|
out:
|
||||||
|
free(secret);
|
||||||
if (wpa_supplicant)
|
if (wpa_supplicant)
|
||||||
fclose(wpa_supplicant);
|
fclose(wpa_supplicant);
|
||||||
umask(oldmask);
|
umask(oldmask);
|
||||||
|
|||||||
@@ -2,9 +2,81 @@
|
|||||||
#include <srx/lyx.h>
|
#include <srx/lyx.h>
|
||||||
|
|
||||||
#include "interfaces.h"
|
#include "interfaces.h"
|
||||||
|
#include "base64.h"
|
||||||
|
|
||||||
#define WIREGUARD_CONFIG "/run/wireguard-%s.conf"
|
#define WIREGUARD_CONFIG "/run/wireguard-%s.conf"
|
||||||
|
|
||||||
|
static int wireguard_validate_psk(sr_session_ctx_t *session, struct lyd_node *cif,
|
||||||
|
const char *ifname, const char *psk_ref)
|
||||||
|
{
|
||||||
|
struct lyd_node *psk_node;
|
||||||
|
const char *psk_data;
|
||||||
|
unsigned char *decoded;
|
||||||
|
size_t len;
|
||||||
|
|
||||||
|
psk_node = lydx_get_xpathf(cif,
|
||||||
|
"../../keystore/symmetric-keys/symmetric-key[name='%s']",
|
||||||
|
psk_ref);
|
||||||
|
if (!psk_node)
|
||||||
|
return SR_ERR_OK;
|
||||||
|
|
||||||
|
psk_data = lydx_get_cattr(psk_node, "cleartext-symmetric-key");
|
||||||
|
if (!psk_data || !*psk_data)
|
||||||
|
return SR_ERR_OK;
|
||||||
|
|
||||||
|
decoded = base64_decode((const unsigned char *)psk_data, strlen(psk_data), &len);
|
||||||
|
if (!decoded)
|
||||||
|
return SR_ERR_OK;
|
||||||
|
|
||||||
|
if (len != 32) {
|
||||||
|
if (session)
|
||||||
|
sr_session_set_error_message(session,
|
||||||
|
"%s: WireGuard preshared key '%s' must be "
|
||||||
|
"exactly 32 bytes, got %zu",
|
||||||
|
ifname, psk_ref, len);
|
||||||
|
free(decoded);
|
||||||
|
return SR_ERR_VALIDATION_FAILED;
|
||||||
|
}
|
||||||
|
|
||||||
|
free(decoded);
|
||||||
|
return SR_ERR_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
int wireguard_validate_peers(sr_session_ctx_t *session, struct lyd_node *cif)
|
||||||
|
{
|
||||||
|
const char *ifname = lydx_get_cattr(cif, "name");
|
||||||
|
struct lyd_node *wg, *bag_peer, *peer;
|
||||||
|
|
||||||
|
wg = lydx_get_child(cif, "wireguard");
|
||||||
|
if (!wg)
|
||||||
|
return SR_ERR_OK;
|
||||||
|
|
||||||
|
LYX_LIST_FOR_EACH(lyd_child(wg), bag_peer, "peers") {
|
||||||
|
const char *psk_ref;
|
||||||
|
int rc;
|
||||||
|
|
||||||
|
/* Validate bag-level PSK */
|
||||||
|
psk_ref = lydx_get_cattr(bag_peer, "preshared-key");
|
||||||
|
if (psk_ref) {
|
||||||
|
rc = wireguard_validate_psk(session, cif, ifname, psk_ref);
|
||||||
|
if (rc)
|
||||||
|
return rc;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Validate per-peer PSK overrides */
|
||||||
|
LYX_LIST_FOR_EACH(lyd_child(bag_peer), peer, "peer") {
|
||||||
|
psk_ref = lydx_get_cattr(peer, "preshared-key");
|
||||||
|
if (psk_ref) {
|
||||||
|
rc = wireguard_validate_psk(session, cif, ifname, psk_ref);
|
||||||
|
if (rc)
|
||||||
|
return rc;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return SR_ERR_OK;
|
||||||
|
}
|
||||||
|
|
||||||
/* Helper to get a peer setting with override logic:
|
/* Helper to get a peer setting with override logic:
|
||||||
* 1. Check peer-specific override
|
* 1. Check peer-specific override
|
||||||
* 2. Fall back to key-bag level default
|
* 2. Fall back to key-bag level default
|
||||||
|
|||||||
@@ -391,9 +391,11 @@ static int netdag_gen_afspec_add(sr_session_ctx_t *session, struct dagger *net,
|
|||||||
case IFT_VXLAN:
|
case IFT_VXLAN:
|
||||||
return vxlan_gen(NULL, cif, ip);
|
return vxlan_gen(NULL, cif, ip);
|
||||||
case IFT_WIFI:
|
case IFT_WIFI:
|
||||||
return wifi_add_iface(cif, net);
|
return wifi_validate_secret(session, cif)
|
||||||
|
? : wifi_add_iface(cif, net);
|
||||||
case IFT_WIREGUARD:
|
case IFT_WIREGUARD:
|
||||||
return wireguard_gen(NULL, cif, ip, net);
|
return wireguard_validate_peers(session, cif)
|
||||||
|
? : wireguard_gen(NULL, cif, ip, net);
|
||||||
case IFT_ETH:
|
case IFT_ETH:
|
||||||
return netdag_gen_ethtool(net, cif, dif);
|
return netdag_gen_ethtool(net, cif, dif);
|
||||||
case IFT_LO:
|
case IFT_LO:
|
||||||
@@ -424,7 +426,8 @@ static int netdag_gen_afspec_set(sr_session_ctx_t *session, struct dagger *net,
|
|||||||
return netdag_gen_ethtool(net, cif, dif);
|
return netdag_gen_ethtool(net, cif, dif);
|
||||||
case IFT_WIFI:
|
case IFT_WIFI:
|
||||||
if (wifi_get_mode(cif) == wifi_station)
|
if (wifi_get_mode(cif) == wifi_station)
|
||||||
return wifi_gen_station(cif);
|
return wifi_validate_secret(session, cif)
|
||||||
|
? : wifi_gen_station(cif);
|
||||||
return 0;
|
return 0;
|
||||||
case IFT_DUMMY:
|
case IFT_DUMMY:
|
||||||
case IFT_GRE:
|
case IFT_GRE:
|
||||||
@@ -819,6 +822,39 @@ err_out:
|
|||||||
return err;
|
return err;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
int interfaces_validate_keys(sr_session_ctx_t *session, struct lyd_node *config)
|
||||||
|
{
|
||||||
|
struct lyd_node *ifaces, *iface;
|
||||||
|
int rc;
|
||||||
|
|
||||||
|
ifaces = lydx_get_descendant(config, "interfaces", "interface", NULL);
|
||||||
|
LYX_LIST_FOR_EACH(ifaces, iface, "interface") {
|
||||||
|
const char *ifname = lydx_get_cattr(iface, "name");
|
||||||
|
|
||||||
|
switch (iftype_from_iface(iface)) {
|
||||||
|
case IFT_WIFI:
|
||||||
|
rc = wifi_validate_secret(session, iface);
|
||||||
|
break;
|
||||||
|
case IFT_WIREGUARD:
|
||||||
|
rc = wireguard_validate_peers(session, iface);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
rc = SR_ERR_OK;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!rc)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
if (session)
|
||||||
|
return rc;
|
||||||
|
|
||||||
|
ERROR("%s: key fails validation, check keystore", ifname);
|
||||||
|
}
|
||||||
|
|
||||||
|
return SR_ERR_OK;
|
||||||
|
}
|
||||||
|
|
||||||
int interfaces_get_all_l3(const struct lyd_node *tree, char ***ifaces)
|
int interfaces_get_all_l3(const struct lyd_node *tree, char ***ifaces)
|
||||||
{
|
{
|
||||||
struct lyd_node *interfaces, *cif;
|
struct lyd_node *interfaces, *cif;
|
||||||
|
|||||||
@@ -105,6 +105,7 @@ const char *get_chassis_addr(void);
|
|||||||
int interface_get_phys_addr(struct lyd_node *cif, char *mac);
|
int interface_get_phys_addr(struct lyd_node *cif, char *mac);
|
||||||
int link_gen_address(struct lyd_node *cif, FILE *ip);
|
int link_gen_address(struct lyd_node *cif, FILE *ip);
|
||||||
int interfaces_get_all_l3(const struct lyd_node *tree, char ***ifaces);
|
int interfaces_get_all_l3(const struct lyd_node *tree, char ***ifaces);
|
||||||
|
int interfaces_validate_keys(sr_session_ctx_t *session, struct lyd_node *config);
|
||||||
|
|
||||||
/* ip.c */
|
/* ip.c */
|
||||||
int netdag_gen_ipv6_autoconf(struct dagger *net, struct lyd_node *cif,
|
int netdag_gen_ipv6_autoconf(struct dagger *net, struct lyd_node *cif,
|
||||||
@@ -130,6 +131,7 @@ typedef enum wifi_mode_t {
|
|||||||
wifi_unknown
|
wifi_unknown
|
||||||
} wifi_mode_t;
|
} wifi_mode_t;
|
||||||
|
|
||||||
|
int wifi_validate_secret(sr_session_ctx_t *session, struct lyd_node *cif);
|
||||||
int wifi_add_iface(struct lyd_node *cif, struct dagger *net);
|
int wifi_add_iface(struct lyd_node *cif, struct dagger *net);
|
||||||
int wifi_del_iface(struct lyd_node *dif, struct dagger *net);
|
int wifi_del_iface(struct lyd_node *dif, struct dagger *net);
|
||||||
int wifi_mode_changed(struct lyd_node *wifi);
|
int wifi_mode_changed(struct lyd_node *wifi);
|
||||||
@@ -162,6 +164,7 @@ int ifchange_cand_infer_dhcp(sr_session_ctx_t *session, const char *path);
|
|||||||
int vxlan_gen(struct lyd_node *dif, struct lyd_node *cif, FILE *ip);
|
int vxlan_gen(struct lyd_node *dif, struct lyd_node *cif, FILE *ip);
|
||||||
|
|
||||||
/* infix-if-wireguard */
|
/* infix-if-wireguard */
|
||||||
|
int wireguard_validate_peers(sr_session_ctx_t *session, struct lyd_node *cif);
|
||||||
int wireguard_gen(struct lyd_node *dif, struct lyd_node *cif, FILE *ip, struct dagger *net);
|
int wireguard_gen(struct lyd_node *dif, struct lyd_node *cif, FILE *ip, struct dagger *net);
|
||||||
|
|
||||||
#endif /* CONFD_INTERFACES_H_ */
|
#endif /* CONFD_INTERFACES_H_ */
|
||||||
|
|||||||
@@ -8,8 +8,10 @@
|
|||||||
|
|
||||||
#include "base64.h"
|
#include "base64.h"
|
||||||
#include "core.h"
|
#include "core.h"
|
||||||
|
#include "interfaces.h"
|
||||||
|
|
||||||
#define XPATH_KEYSTORE_ "/ietf-keystore:keystore/asymmetric-keys"
|
#define XPATH_KEYSTORE_ASYM "/ietf-keystore:keystore/asymmetric-keys"
|
||||||
|
#define XPATH_KEYSTORE_SYM "/ietf-keystore:keystore/symmetric-keys"
|
||||||
#define SSH_PRIVATE_KEY "/tmp/ssh.key"
|
#define SSH_PRIVATE_KEY "/tmp/ssh.key"
|
||||||
#define SSH_PUBLIC_KEY "/tmp/ssh.pub"
|
#define SSH_PUBLIC_KEY "/tmp/ssh.pub"
|
||||||
|
|
||||||
@@ -166,7 +168,8 @@ int keystore_change(sr_session_ctx_t *session, struct lyd_node *config, struct l
|
|||||||
struct lyd_node *changes, *change;
|
struct lyd_node *changes, *change;
|
||||||
int rc = SR_ERR_OK;
|
int rc = SR_ERR_OK;
|
||||||
|
|
||||||
if (diff && !lydx_find_xpathf(diff, XPATH_KEYSTORE_))
|
if (diff && !lydx_find_xpathf(diff, XPATH_KEYSTORE_ASYM)
|
||||||
|
&& !lydx_find_xpathf(diff, XPATH_KEYSTORE_SYM))
|
||||||
return SR_ERR_OK;
|
return SR_ERR_OK;
|
||||||
|
|
||||||
switch (event) {
|
switch (event) {
|
||||||
@@ -174,6 +177,9 @@ int keystore_change(sr_session_ctx_t *session, struct lyd_node *config, struct l
|
|||||||
rc = keystore_update(session, config, diff);
|
rc = keystore_update(session, config, diff);
|
||||||
break;
|
break;
|
||||||
case SR_EV_CHANGE:
|
case SR_EV_CHANGE:
|
||||||
|
if (diff && lydx_find_xpathf(diff, XPATH_KEYSTORE_SYM))
|
||||||
|
rc = interfaces_validate_keys(session, config);
|
||||||
|
break;
|
||||||
case SR_EV_ENABLED:
|
case SR_EV_ENABLED:
|
||||||
break;
|
break;
|
||||||
case SR_EV_ABORT:
|
case SR_EV_ABORT:
|
||||||
@@ -186,6 +192,8 @@ int keystore_change(sr_session_ctx_t *session, struct lyd_node *config, struct l
|
|||||||
if (rename(SSH_HOSTKEYS_NEXT, SSH_HOSTKEYS))
|
if (rename(SSH_HOSTKEYS_NEXT, SSH_HOSTKEYS))
|
||||||
ERRNO("Failed switching to new %s", SSH_HOSTKEYS);
|
ERRNO("Failed switching to new %s", SSH_HOSTKEYS);
|
||||||
}
|
}
|
||||||
|
if (diff && lydx_find_xpathf(diff, XPATH_KEYSTORE_SYM))
|
||||||
|
interfaces_validate_keys(NULL, config);
|
||||||
return SR_ERR_OK;
|
return SR_ERR_OK;
|
||||||
default:
|
default:
|
||||||
return SR_ERR_OK;
|
return SR_ERR_OK;
|
||||||
|
|||||||
@@ -15,32 +15,46 @@ module infix-crypto-types {
|
|||||||
revision 2025-02-04 {
|
revision 2025-02-04 {
|
||||||
description "Initial";
|
description "Initial";
|
||||||
}
|
}
|
||||||
|
|
||||||
identity private-key-format {
|
identity private-key-format {
|
||||||
|
base ct:private-key-format;
|
||||||
description
|
description
|
||||||
"Base key-format identity for private keys.";
|
"Base for Infix private key format extensions.";
|
||||||
}
|
}
|
||||||
|
|
||||||
identity public-key-format {
|
identity public-key-format {
|
||||||
|
base ct:public-key-format;
|
||||||
description
|
description
|
||||||
"Base key-format identity for public keys.";
|
"Base for Infix public key format extensions.";
|
||||||
}
|
}
|
||||||
|
|
||||||
identity rsa-private-key-format {
|
identity rsa-private-key-format {
|
||||||
base private-key-format;
|
base private-key-format;
|
||||||
base ct:rsa-private-key-format;
|
base ct:rsa-private-key-format;
|
||||||
|
description
|
||||||
|
"RSA private key in PKCS#1 format (RFC 8017).
|
||||||
|
Used for SSH host keys.";
|
||||||
}
|
}
|
||||||
|
|
||||||
identity ssh-public-key-format {
|
identity ssh-public-key-format {
|
||||||
base public-key-format;
|
base public-key-format;
|
||||||
base ct:ssh-public-key-format;
|
base ct:ssh-public-key-format;
|
||||||
|
description
|
||||||
|
"SSH public key format (RFC 4253, Section 6.6).
|
||||||
|
Used for SSH host keys.";
|
||||||
}
|
}
|
||||||
|
|
||||||
identity symmetric-key-format {
|
identity symmetric-key-format {
|
||||||
|
base ct:symmetric-key-format;
|
||||||
description
|
description
|
||||||
"Base for symmetric key format";
|
"Base for Infix symmetric key format extensions.";
|
||||||
}
|
}
|
||||||
identity wifi-preshared-key-format {
|
|
||||||
|
identity passphrase-key-format {
|
||||||
base ct:symmetric-key-format;
|
base ct:symmetric-key-format;
|
||||||
base symmetric-key-format;
|
base symmetric-key-format;
|
||||||
description
|
description
|
||||||
"WiFi secret key";
|
"Human-readable passphrase, e.g., WiFi WPA2/WPA3 passwords.";
|
||||||
}
|
}
|
||||||
|
|
||||||
identity x25519-public-key-format {
|
identity x25519-public-key-format {
|
||||||
@@ -48,7 +62,7 @@ module infix-crypto-types {
|
|||||||
base ct:public-key-format;
|
base ct:public-key-format;
|
||||||
description
|
description
|
||||||
"X25519 (Curve25519) public key format for Diffie-Hellman key exchange.
|
"X25519 (Curve25519) public key format for Diffie-Hellman key exchange.
|
||||||
This is the format used by WireGuard.";
|
This format is used by network protocols such as WireGuard VPN.";
|
||||||
}
|
}
|
||||||
|
|
||||||
identity x25519-private-key-format {
|
identity x25519-private-key-format {
|
||||||
@@ -56,15 +70,6 @@ module infix-crypto-types {
|
|||||||
base ct:private-key-format;
|
base ct:private-key-format;
|
||||||
description
|
description
|
||||||
"X25519 (Curve25519) private key format for Diffie-Hellman key exchange.
|
"X25519 (Curve25519) private key format for Diffie-Hellman key exchange.
|
||||||
This is the format used by WireGuard.";
|
This format is used by network protocols such as WireGuard VPN.";
|
||||||
}
|
|
||||||
|
|
||||||
identity wireguard-symmetric-key-format {
|
|
||||||
base ct:symmetric-key-format;
|
|
||||||
base symmetric-key-format;
|
|
||||||
description
|
|
||||||
"WireGuard pre-shared key format.
|
|
||||||
32-byte base64-encoded key used as an optional additional layer
|
|
||||||
of symmetric encryption for post-quantum resistance.";
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,6 +10,9 @@ submodule infix-if-wireguard {
|
|||||||
import ietf-inet-types {
|
import ietf-inet-types {
|
||||||
prefix inet;
|
prefix inet;
|
||||||
}
|
}
|
||||||
|
import ietf-crypto-types {
|
||||||
|
prefix ct;
|
||||||
|
}
|
||||||
import infix-crypto-types {
|
import infix-crypto-types {
|
||||||
prefix ixct;
|
prefix ixct;
|
||||||
}
|
}
|
||||||
@@ -75,8 +78,8 @@ submodule infix-if-wireguard {
|
|||||||
|
|
||||||
This provides post-quantum resistance as an attacker would need
|
This provides post-quantum resistance as an attacker would need
|
||||||
to break both the Curve25519 key exchange and this symmetric key.";
|
to break both the Curve25519 key exchange and this symmetric key.";
|
||||||
must "derived-from-or-self(deref(.)/../infix-ks:key-format, 'ixct:wireguard-symmetric-key-format')" {
|
must "derived-from-or-self(deref(.)/../ks:key-format, 'ct:octet-string-key-format')" {
|
||||||
error-message "Preshared key must be in wireguard-symmetric-key-format";
|
error-message "Preshared key must be in octet-string-key-format";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -5,15 +5,12 @@ module infix-keystore {
|
|||||||
import ietf-keystore {
|
import ietf-keystore {
|
||||||
prefix ks;
|
prefix ks;
|
||||||
}
|
}
|
||||||
import ietf-crypto-types {
|
|
||||||
prefix ct;
|
|
||||||
}
|
|
||||||
import infix-crypto-types {
|
import infix-crypto-types {
|
||||||
prefix infix-ct;
|
prefix infix-ct;
|
||||||
}
|
}
|
||||||
|
|
||||||
revision 2025-12-17 {
|
revision 2025-12-17 {
|
||||||
description "Add WireGuard support";
|
description "Add WireGuard support, see infix-crypto-types.yang";
|
||||||
}
|
}
|
||||||
revision 2025-12-10 {
|
revision 2025-12-10 {
|
||||||
description "Adapt to changes in final version of ietf-keystore";
|
description "Adapt to changes in final version of ietf-keystore";
|
||||||
@@ -24,59 +21,4 @@ module infix-keystore {
|
|||||||
revision 2025-02-04 {
|
revision 2025-02-04 {
|
||||||
description "Initial";
|
description "Initial";
|
||||||
}
|
}
|
||||||
deviation "/ks:keystore/ks:asymmetric-keys/ks:asymmetric-key/ks:public-key-format" {
|
|
||||||
deviate replace {
|
|
||||||
type identityref {
|
|
||||||
base infix-ct:public-key-format;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
deviation "/ks:keystore/ks:asymmetric-keys/ks:asymmetric-key/ks:private-key-format" {
|
|
||||||
deviate replace {
|
|
||||||
type identityref {
|
|
||||||
base infix-ct:private-key-format;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
deviation "/ks:keystore/ks:symmetric-keys/ks:symmetric-key/ks:key-format" {
|
|
||||||
deviate not-supported;
|
|
||||||
}
|
|
||||||
augment "/ks:keystore/ks:symmetric-keys/ks:symmetric-key" {
|
|
||||||
leaf key-format {
|
|
||||||
type identityref {
|
|
||||||
base infix-ct:symmetric-key-format;
|
|
||||||
}
|
|
||||||
description
|
|
||||||
"Identifies the symmetric key's format
|
|
||||||
|
|
||||||
Valid symmetric key formats are:
|
|
||||||
wifi-preshared-key-format - WiFi preshared key
|
|
||||||
wireguard-symmetric-key-format - WireGuard preshared key";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
deviation "/ks:keystore/ks:symmetric-keys/ks:symmetric-key/ks:key-type/ks:cleartext-symmetric-key" {
|
|
||||||
deviate not-supported;
|
|
||||||
}
|
|
||||||
augment "/ks:keystore/ks:symmetric-keys/ks:symmetric-key/ks:key-type" {
|
|
||||||
case cleartext-symmetric-key {
|
|
||||||
leaf symmetric-key {
|
|
||||||
type string;
|
|
||||||
must "../infix-ks:key-format != 'infix-ct:wifi-preshared-key-format' or " +
|
|
||||||
"(string-length(.) >= 8 and string-length(.) <= 63)" {
|
|
||||||
error-message "WiFi pre-shared key must be 8-63 characters long";
|
|
||||||
}
|
|
||||||
must "../infix-ks:key-format != 'infix-ct:wireguard-symmetric-key-format' or " +
|
|
||||||
"string-length(.) = 44" {
|
|
||||||
error-message "WireGuard pre-shared key must be 44 characters (32-byte base64-encoded)";
|
|
||||||
}
|
|
||||||
description
|
|
||||||
"Cleartext symmetric key value.
|
|
||||||
|
|
||||||
Format depends on key-format:
|
|
||||||
- WiFi pre-shared key: 8-63 printable ASCII characters
|
|
||||||
- WireGuard pre-shared key: 32-byte base64-encoded key (44 chars with padding)";
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -413,6 +413,20 @@ int infix_groups(kcontext_t *ctx)
|
|||||||
"| jq -r '.\"ietf-netconf-acm:nacm\".groups.group[].name'");
|
"| jq -r '.\"ietf-netconf-acm:nacm\".groups.group[].name'");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
int infix_sym_keys(kcontext_t *ctx)
|
||||||
|
{
|
||||||
|
(void)ctx;
|
||||||
|
return shellf("copy running -x /ietf-keystore:keystore/symmetric-keys "
|
||||||
|
"| jq -r '.\"ietf-keystore:keystore\".\"symmetric-keys\".\"symmetric-key\"[].name'");
|
||||||
|
}
|
||||||
|
|
||||||
|
int infix_asym_keys(kcontext_t *ctx)
|
||||||
|
{
|
||||||
|
(void)ctx;
|
||||||
|
return shellf("copy running -x /ietf-keystore:keystore/asymmetric-keys "
|
||||||
|
"| jq -r '.\"ietf-keystore:keystore\".\"asymmetric-keys\".\"asymmetric-key\"[].name'");
|
||||||
|
}
|
||||||
|
|
||||||
int kplugin_infix_fini(kcontext_t *ctx)
|
int kplugin_infix_fini(kcontext_t *ctx)
|
||||||
{
|
{
|
||||||
(void)ctx;
|
(void)ctx;
|
||||||
@@ -432,6 +446,8 @@ int kplugin_infix_init(kcontext_t *ctx)
|
|||||||
kplugin_add_syms(plugin, ksym_new("ifaces", infix_ifaces));
|
kplugin_add_syms(plugin, ksym_new("ifaces", infix_ifaces));
|
||||||
kplugin_add_syms(plugin, ksym_new("users", infix_users));
|
kplugin_add_syms(plugin, ksym_new("users", infix_users));
|
||||||
kplugin_add_syms(plugin, ksym_new("groups", infix_groups));
|
kplugin_add_syms(plugin, ksym_new("groups", infix_groups));
|
||||||
|
kplugin_add_syms(plugin, ksym_new("sym_keys", infix_sym_keys));
|
||||||
|
kplugin_add_syms(plugin, ksym_new("asym_keys", infix_asym_keys));
|
||||||
kplugin_add_syms(plugin, ksym_new("firewall_zones", infix_firewall_zones));
|
kplugin_add_syms(plugin, ksym_new("firewall_zones", infix_firewall_zones));
|
||||||
kplugin_add_syms(plugin, ksym_new("firewall_policies", infix_firewall_policies));
|
kplugin_add_syms(plugin, ksym_new("firewall_policies", infix_firewall_policies));
|
||||||
kplugin_add_syms(plugin, ksym_new("firewall_services", infix_firewall_services));
|
kplugin_add_syms(plugin, ksym_new("firewall_services", infix_firewall_services));
|
||||||
|
|||||||
@@ -155,6 +155,20 @@
|
|||||||
<ACTION sym="STRING"/>
|
<ACTION sym="STRING"/>
|
||||||
</PTYPE>
|
</PTYPE>
|
||||||
|
|
||||||
|
<PTYPE name="SYM_KEYS">
|
||||||
|
<COMPL>
|
||||||
|
<ACTION sym="sym_keys@infix"/>
|
||||||
|
</COMPL>
|
||||||
|
<ACTION sym="STRING"/>
|
||||||
|
</PTYPE>
|
||||||
|
|
||||||
|
<PTYPE name="ASYM_KEYS">
|
||||||
|
<COMPL>
|
||||||
|
<ACTION sym="asym_keys@infix"/>
|
||||||
|
</COMPL>
|
||||||
|
<ACTION sym="STRING"/>
|
||||||
|
</PTYPE>
|
||||||
|
|
||||||
<VIEW name="main">
|
<VIEW name="main">
|
||||||
<HOTKEY key="^D" cmd="exit"/>
|
<HOTKEY key="^D" cmd="exit"/>
|
||||||
|
|
||||||
@@ -259,6 +273,21 @@
|
|||||||
</COMMAND>
|
</COMMAND>
|
||||||
</COMMAND>
|
</COMMAND>
|
||||||
|
|
||||||
|
<COMMAND name="wireguard" help="WireGuard VPN key tools" mode="switch">
|
||||||
|
<COMMAND name="genkey" help="Generate a WireGuard private/public key pair">
|
||||||
|
<ACTION sym="script">
|
||||||
|
priv=$(wg genkey)
|
||||||
|
pub=$(printf '%s' "$priv" | wg pubkey)
|
||||||
|
echo "Private: $priv"
|
||||||
|
echo "Public: $pub"
|
||||||
|
</ACTION>
|
||||||
|
</COMMAND>
|
||||||
|
|
||||||
|
<COMMAND name="genpsk" help="Generate a WireGuard pre-shared key">
|
||||||
|
<ACTION sym="script">wg genpsk</ACTION>
|
||||||
|
</COMMAND>
|
||||||
|
</COMMAND>
|
||||||
|
|
||||||
<COMMAND name="set" help="Set operations, e.g., current date/time" mode="switch">
|
<COMMAND name="set" help="Set operations, e.g., current date/time" mode="switch">
|
||||||
<COMMAND name="datetime" help="Set current date and time, ISO-8601 format">
|
<COMMAND name="datetime" help="Set current date and time, ISO-8601 format">
|
||||||
<PARAM name="current-datetime" ptype="/STRING" help="yyyy-mm-ddThh:mm:ss(Z|+/-hh:mm)"/>
|
<PARAM name="current-datetime" ptype="/STRING" help="yyyy-mm-ddThh:mm:ss(Z|+/-hh:mm)"/>
|
||||||
@@ -338,6 +367,20 @@
|
|||||||
</ACTION>
|
</ACTION>
|
||||||
</COMMAND>
|
</COMMAND>
|
||||||
|
|
||||||
|
<COMMAND name="keystore" help="Show keystore keys">
|
||||||
|
<SWITCH name="subcommands" min="0">
|
||||||
|
<COMMAND name="symmetric" help="Show details for a symmetric key">
|
||||||
|
<PARAM name="name" ptype="/SYM_KEYS" help="Key name"/>
|
||||||
|
<ACTION sym="script" in="tty" out="tty" interrupt="true">show keystore symmetric $KLISH_PARAM_name</ACTION>
|
||||||
|
</COMMAND>
|
||||||
|
<COMMAND name="asymmetric" help="Show details for an asymmetric key">
|
||||||
|
<PARAM name="name" ptype="/ASYM_KEYS" help="Key name"/>
|
||||||
|
<ACTION sym="script" in="tty" out="tty" interrupt="true">show keystore asymmetric $KLISH_PARAM_name</ACTION>
|
||||||
|
</COMMAND>
|
||||||
|
</SWITCH>
|
||||||
|
<ACTION sym="script" in="tty" out="tty" interrupt="true">show keystore</ACTION>
|
||||||
|
</COMMAND>
|
||||||
|
|
||||||
<COMMAND name="nacm" help="Show users and NACM status and groups">
|
<COMMAND name="nacm" help="Show users and NACM status and groups">
|
||||||
<SWITCH name="subcommands" min="0">
|
<SWITCH name="subcommands" min="0">
|
||||||
<COMMAND name="group" help="Show details for a specific NACM group">
|
<COMMAND name="group" help="Show details for a specific NACM group">
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python3
|
||||||
|
import base64
|
||||||
import json
|
import json
|
||||||
import argparse
|
import argparse
|
||||||
import sys
|
import sys
|
||||||
@@ -1030,6 +1031,7 @@ class DhcpServer:
|
|||||||
class Iface:
|
class Iface:
|
||||||
# Class variable to hold routing-enabled interfaces for the current display session
|
# Class variable to hold routing-enabled interfaces for the current display session
|
||||||
_routing_ifaces = set()
|
_routing_ifaces = set()
|
||||||
|
_keystore = {}
|
||||||
|
|
||||||
def __init__(self, data):
|
def __init__(self, data):
|
||||||
self.data = data
|
self.data = data
|
||||||
@@ -3381,6 +3383,131 @@ def show_nacm_user(json):
|
|||||||
print("For detailed rules, use: show nacm group <name>")
|
print("For detailed rules, use: show nacm group <name>")
|
||||||
|
|
||||||
|
|
||||||
|
def _keystore_format_name(key_format):
|
||||||
|
"""Simplify key-format to a short display name."""
|
||||||
|
fmt = key_format.split(':')[-1] if ':' in key_format else key_format
|
||||||
|
return fmt.replace('-key-format', '')
|
||||||
|
|
||||||
|
|
||||||
|
def _keystore_decode_symmetric(key):
|
||||||
|
"""Decode a symmetric key value for display."""
|
||||||
|
key_format = key.get('key-format', '')
|
||||||
|
fmt = _keystore_format_name(key_format)
|
||||||
|
b64val = key.get('cleartext-symmetric-key', '')
|
||||||
|
|
||||||
|
if fmt == 'passphrase' and b64val:
|
||||||
|
try:
|
||||||
|
return base64.b64decode(b64val).decode('utf-8')
|
||||||
|
except Exception:
|
||||||
|
return b64val
|
||||||
|
return b64val if b64val else '-'
|
||||||
|
|
||||||
|
|
||||||
|
def _keystore_find_key(keystore, kind, name):
|
||||||
|
"""Find a key by type and name in the keystore."""
|
||||||
|
if kind == 'symmetric':
|
||||||
|
keys = keystore.get('symmetric-keys', {}).get('symmetric-key', [])
|
||||||
|
else:
|
||||||
|
keys = keystore.get('asymmetric-keys', {}).get('asymmetric-key', [])
|
||||||
|
for key in keys:
|
||||||
|
if key.get('name') == name:
|
||||||
|
return key
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def _keystore_asym_type(key):
|
||||||
|
"""Derive asymmetric key algorithm from key format fields."""
|
||||||
|
for field in ('private-key-format', 'public-key-format'):
|
||||||
|
fmt = key.get(field, '')
|
||||||
|
name = fmt.split(':')[-1] if ':' in fmt else fmt
|
||||||
|
name = name.replace('-private-key-format', '').replace('-public-key-format', '')
|
||||||
|
if name:
|
||||||
|
return name
|
||||||
|
return ''
|
||||||
|
|
||||||
|
|
||||||
|
def show_keystore_detail(keystore, kind, name):
|
||||||
|
"""Display detailed information about a specific key."""
|
||||||
|
key = _keystore_find_key(keystore, kind, name)
|
||||||
|
if not key:
|
||||||
|
print(f'{kind.capitalize()} key "{name}" not found.')
|
||||||
|
return
|
||||||
|
|
||||||
|
print(f"{'name':<{20}}: {name}")
|
||||||
|
if kind == 'symmetric':
|
||||||
|
fmt = _keystore_format_name(key.get('key-format', ''))
|
||||||
|
value = _keystore_decode_symmetric(key)
|
||||||
|
print(f"{'format':<{20}}: {fmt}")
|
||||||
|
print(f"{'value':<{20}}: {value}")
|
||||||
|
else:
|
||||||
|
ktype = _keystore_asym_type(key)
|
||||||
|
if ktype:
|
||||||
|
print(f"{'algorithm':<{20}}: {ktype}")
|
||||||
|
pub_fmt = _keystore_format_name(key.get('public-key-format', ''))
|
||||||
|
if pub_fmt:
|
||||||
|
print(f"{'public key format':<{20}}: {pub_fmt}")
|
||||||
|
pub_key = key.get('public-key', '')
|
||||||
|
if pub_key:
|
||||||
|
print(f"{'public key':<{20}}: {pub_key}")
|
||||||
|
|
||||||
|
|
||||||
|
def show_keystore(json, kind=None, name=None):
|
||||||
|
"""Display keystore keys overview or detail for a specific key."""
|
||||||
|
keystore = json.get("ietf-keystore:keystore", {})
|
||||||
|
if not keystore:
|
||||||
|
print("Keystore is empty.")
|
||||||
|
return
|
||||||
|
|
||||||
|
if kind and name:
|
||||||
|
show_keystore_detail(keystore, kind, name)
|
||||||
|
return
|
||||||
|
|
||||||
|
TABLE_WIDTH = 72
|
||||||
|
|
||||||
|
# Symmetric keys
|
||||||
|
sym_keys_data = keystore.get('symmetric-keys', {}).get('symmetric-key', [])
|
||||||
|
if sym_keys_data:
|
||||||
|
Decore.title("Symmetric Keys", TABLE_WIDTH)
|
||||||
|
table = SimpleTable([
|
||||||
|
Column('NAME', flexible=True),
|
||||||
|
Column('FORMAT'),
|
||||||
|
Column('VALUE', flexible=True)
|
||||||
|
], min_width=TABLE_WIDTH)
|
||||||
|
|
||||||
|
for key in sym_keys_data:
|
||||||
|
name = key.get('name', '')
|
||||||
|
fmt = _keystore_format_name(key.get('key-format', ''))
|
||||||
|
value = _keystore_decode_symmetric(key)
|
||||||
|
table.row(name, fmt, value)
|
||||||
|
|
||||||
|
table.print()
|
||||||
|
|
||||||
|
# Asymmetric keys
|
||||||
|
asym_keys_data = keystore.get('asymmetric-keys', {}).get('asymmetric-key', [])
|
||||||
|
if asym_keys_data:
|
||||||
|
Decore.title("Asymmetric Keys", TABLE_WIDTH)
|
||||||
|
table = SimpleTable([
|
||||||
|
Column('NAME', flexible=True),
|
||||||
|
Column('TYPE'),
|
||||||
|
Column('PUBLIC KEY', flexible=True)
|
||||||
|
], min_width=TABLE_WIDTH)
|
||||||
|
|
||||||
|
for key in asym_keys_data:
|
||||||
|
name = key.get('name', '')
|
||||||
|
ktype = _keystore_asym_type(key)
|
||||||
|
|
||||||
|
pub_key = key.get('public-key', '')
|
||||||
|
if len(pub_key) > 40:
|
||||||
|
pub_key = pub_key[:37] + '...'
|
||||||
|
|
||||||
|
table.row(name, ktype, pub_key)
|
||||||
|
|
||||||
|
table.print()
|
||||||
|
|
||||||
|
if not sym_keys_data and not asym_keys_data:
|
||||||
|
print("Keystore is empty.")
|
||||||
|
|
||||||
|
|
||||||
def show_system(json):
|
def show_system(json):
|
||||||
"""System information overivew"""
|
"""System information overivew"""
|
||||||
if not json.get("ietf-system:system-state"):
|
if not json.get("ietf-system:system-state"):
|
||||||
@@ -5369,6 +5496,10 @@ def main():
|
|||||||
subparsers.add_parser('show-nacm-group', help='Show NACM group details')
|
subparsers.add_parser('show-nacm-group', help='Show NACM group details')
|
||||||
subparsers.add_parser('show-nacm-user', help='Show NACM user details')
|
subparsers.add_parser('show-nacm-user', help='Show NACM user details')
|
||||||
|
|
||||||
|
ks_parser = subparsers.add_parser('show-keystore', help='Show keystore keys')
|
||||||
|
ks_parser.add_argument('-t', '--type', help='Key type (symmetric or asymmetric)')
|
||||||
|
ks_parser.add_argument('-n', '--name', help='Key name')
|
||||||
|
|
||||||
subparsers.add_parser('show-ntp', help='Show NTP status') \
|
subparsers.add_parser('show-ntp', help='Show NTP status') \
|
||||||
.add_argument('-a', '--address', help='Show details for specific address')
|
.add_argument('-a', '--address', help='Show details for specific address')
|
||||||
subparsers.add_parser('show-ntp-tracking', help='Show NTP tracking status')
|
subparsers.add_parser('show-ntp-tracking', help='Show NTP tracking status')
|
||||||
@@ -5438,6 +5569,8 @@ def main():
|
|||||||
show_nacm_group(json_data)
|
show_nacm_group(json_data)
|
||||||
elif args.command == "show-nacm-user":
|
elif args.command == "show-nacm-user":
|
||||||
show_nacm_user(json_data)
|
show_nacm_user(json_data)
|
||||||
|
elif args.command == "show-keystore":
|
||||||
|
show_keystore(json_data, getattr(args, 'type', None), args.name)
|
||||||
elif args.command == "show-ntp":
|
elif args.command == "show-ntp":
|
||||||
show_ntp(json_data, args.address)
|
show_ntp(json_data, args.address)
|
||||||
elif args.command == "show-ntp-tracking":
|
elif args.command == "show-ntp-tracking":
|
||||||
|
|||||||
@@ -75,12 +75,12 @@ def configure_server(dut):
|
|||||||
"symmetric-keys": {
|
"symmetric-keys": {
|
||||||
"symmetric-key": [{
|
"symmetric-key": [{
|
||||||
"name": "psk-client1",
|
"name": "psk-client1",
|
||||||
"infix-keystore:symmetric-key": psk_client1,
|
"cleartext-symmetric-key": psk_client1,
|
||||||
"infix-keystore:key-format": "infix-crypto-types:wireguard-symmetric-key-format"
|
"key-format": "ietf-crypto-types:octet-string-key-format"
|
||||||
}, {
|
}, {
|
||||||
"name": "psk-client2",
|
"name": "psk-client2",
|
||||||
"infix-keystore:symmetric-key": psk_client2,
|
"cleartext-symmetric-key": psk_client2,
|
||||||
"infix-keystore:key-format": "infix-crypto-types:wireguard-symmetric-key-format"
|
"key-format": "ietf-crypto-types:octet-string-key-format"
|
||||||
}]
|
}]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -226,8 +226,8 @@ def configure_client1(dut):
|
|||||||
"symmetric-keys": {
|
"symmetric-keys": {
|
||||||
"symmetric-key": [{
|
"symmetric-key": [{
|
||||||
"name": "psk-server",
|
"name": "psk-server",
|
||||||
"infix-keystore:symmetric-key": psk_client1,
|
"cleartext-symmetric-key": psk_client1,
|
||||||
"infix-keystore:key-format": "infix-crypto-types:wireguard-symmetric-key-format"
|
"key-format": "ietf-crypto-types:octet-string-key-format"
|
||||||
}]
|
}]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -360,8 +360,8 @@ def configure_client2(dut):
|
|||||||
"symmetric-keys": {
|
"symmetric-keys": {
|
||||||
"symmetric-key": [{
|
"symmetric-key": [{
|
||||||
"name": "psk-server",
|
"name": "psk-server",
|
||||||
"infix-keystore:symmetric-key": psk_client2,
|
"cleartext-symmetric-key": psk_client2,
|
||||||
"infix-keystore:key-format": "infix-crypto-types:wireguard-symmetric-key-format"
|
"key-format": "ietf-crypto-types:octet-string-key-format"
|
||||||
}]
|
}]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -22,6 +22,12 @@ may be supplied in places where a hostname is otherwise expected.
|
|||||||
|
|
||||||
Commands:
|
Commands:
|
||||||
|
|
||||||
|
scan [-a] [<iface>]
|
||||||
|
Discover Infix devices on the LAN using mDNS-SD (avahi-browse).
|
||||||
|
Use -a to show all mDNS devices, not just Infix. Falls back to
|
||||||
|
IPv6 link-local multicast ping if avahi-browse is not installed
|
||||||
|
and <iface> is specified.
|
||||||
|
|
||||||
peer <iface>
|
peer <iface>
|
||||||
Return the address of the first IPv6 neighbor to respond on
|
Return the address of the first IPv6 neighbor to respond on
|
||||||
<iface>'s local LAN.
|
<iface>'s local LAN.
|
||||||
@@ -75,6 +81,9 @@ case "$cmd" in
|
|||||||
help)
|
help)
|
||||||
usage && exit 0
|
usage && exit 0
|
||||||
;;
|
;;
|
||||||
|
scan)
|
||||||
|
llscan "$@"
|
||||||
|
;;
|
||||||
peer)
|
peer)
|
||||||
llpeer "$@"
|
llpeer "$@"
|
||||||
;;
|
;;
|
||||||
|
|||||||
+152
@@ -144,3 +144,155 @@ llscp()
|
|||||||
|
|
||||||
$sshpasscmd scp $LLSCP_OPTS "$src" "$dst"
|
$sshpasscmd scp $LLSCP_OPTS "$src" "$dst"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# Usage: llscan [-a] [<iface>]
|
||||||
|
#
|
||||||
|
# Discover Infix devices (or all mDNS devices with -a) on the LAN.
|
||||||
|
# Prefers mDNS-SD via avahi-browse; falls back to IPv6 link-local
|
||||||
|
# multicast ping if avahi-browse is not available and an interface
|
||||||
|
# is specified.
|
||||||
|
llscan()
|
||||||
|
{
|
||||||
|
local all=0
|
||||||
|
local iface=
|
||||||
|
|
||||||
|
while [ $# -gt 0 ]; do
|
||||||
|
case "$1" in
|
||||||
|
-a)
|
||||||
|
all=1
|
||||||
|
shift
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
iface="$1"
|
||||||
|
shift
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
|
||||||
|
if [ "$iface" ] && [ ! -d "/sys/class/net/$iface" ]; then
|
||||||
|
echo "Error: Interface \"$iface\" does not exist." >&2
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if command -v avahi-browse >/dev/null 2>&1; then
|
||||||
|
llscan_mdns "$all"
|
||||||
|
elif [ "$iface" ]; then
|
||||||
|
echo "Note: avahi-browse not found, falling back to link-local scan." >&2
|
||||||
|
llscan_ll "$iface"
|
||||||
|
else
|
||||||
|
cat >&2 <<-EOF
|
||||||
|
Error: avahi-browse not found.
|
||||||
|
|
||||||
|
Install avahi-utils to scan for Infix devices via mDNS:
|
||||||
|
sudo apt install avahi-utils
|
||||||
|
|
||||||
|
Or specify an interface to use IPv6 link-local fallback:
|
||||||
|
$(basename $0) scan <iface>
|
||||||
|
EOF
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
llscan_mdns()
|
||||||
|
{
|
||||||
|
local all="$1"
|
||||||
|
local flags="-tarp"
|
||||||
|
|
||||||
|
if avahi-browse --help 2>&1 | grep -q -- '-k'; then
|
||||||
|
flags="-tarpk"
|
||||||
|
fi
|
||||||
|
|
||||||
|
avahi-browse $flags | awk -F';' -v show_all="$all" '
|
||||||
|
$1 == "=" {
|
||||||
|
host = $7
|
||||||
|
proto = $3
|
||||||
|
addr = $8
|
||||||
|
txt = $10
|
||||||
|
|
||||||
|
on = ""; ov = ""; product = ""; serial = ""; devid = ""
|
||||||
|
n = split(txt, parts, "\" \"")
|
||||||
|
for (i = 1; i <= n; i++) {
|
||||||
|
gsub(/"/, "", parts[i])
|
||||||
|
if (parts[i] ~ /^on=/) { split(parts[i], kv, "="); on = kv[2] }
|
||||||
|
else if (parts[i] ~ /^ov=/) { split(parts[i], kv, "="); ov = kv[2] }
|
||||||
|
else if (parts[i] ~ /^product=/) { split(parts[i], kv, "="); product = kv[2] }
|
||||||
|
else if (parts[i] ~ /^serial=/) { split(parts[i], kv, "="); serial = kv[2] }
|
||||||
|
else if (parts[i] ~ /^deviceid=/) { split(parts[i], kv, "="); devid = kv[2] }
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!show_all && on != "Infix") next
|
||||||
|
|
||||||
|
# Use deviceid (MAC) as unique key; fall back to hostname
|
||||||
|
key = devid ? devid : host
|
||||||
|
|
||||||
|
if (!product) product = on ? on : "-"
|
||||||
|
if (!ov) ov = "-"
|
||||||
|
if (!serial || serial == "null") serial = "-"
|
||||||
|
|
||||||
|
# Prefer IPv4 for display address
|
||||||
|
if (proto == "IPv4") {
|
||||||
|
ipv4[key] = addr
|
||||||
|
} else if (proto == "IPv6" && !ipv6[key]) {
|
||||||
|
ipv6[key] = addr
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!seen[key]++) {
|
||||||
|
keys[++ndevs] = key
|
||||||
|
hosts[key] = host
|
||||||
|
products[key] = product
|
||||||
|
versions[key] = ov
|
||||||
|
serials[key] = serial
|
||||||
|
} else if (length(host) > length(hosts[key])) {
|
||||||
|
# Prefer the unique hostname (e.g., infix-c0-ff-ee.local)
|
||||||
|
# over the generic one (e.g., infix.local)
|
||||||
|
hosts[key] = host
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
END {
|
||||||
|
if (ndevs == 0) {
|
||||||
|
if (show_all)
|
||||||
|
print "No mDNS devices found." | "cat >&2"
|
||||||
|
else
|
||||||
|
print "No Infix devices found. Use -a to show all mDNS devices." | "cat >&2"
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt = "%-26s %-18s %-24s %-22s %s\n"
|
||||||
|
hdr = sprintf(fmt, "HOSTNAME", "ADDRESS", "PRODUCT", "VERSION", "SERIAL")
|
||||||
|
sub(/\n$/, "", hdr)
|
||||||
|
printf "\033[7m%s\033[0m\n", hdr
|
||||||
|
|
||||||
|
for (i = 1; i <= ndevs; i++) {
|
||||||
|
k = keys[i]
|
||||||
|
a = (ipv4[k] ? ipv4[k] : (ipv6[k] ? ipv6[k] : "-"))
|
||||||
|
p = products[k]; if (length(p) > 23) p = substr(p, 1, 22) "~"
|
||||||
|
v = versions[k]; if (length(v) > 21) v = substr(v, 1, 20) "~"
|
||||||
|
printf fmt, hosts[k], a, p, v, serials[k]
|
||||||
|
}
|
||||||
|
|
||||||
|
printf "\n%d device(s) found.\n", ndevs
|
||||||
|
}
|
||||||
|
'
|
||||||
|
}
|
||||||
|
|
||||||
|
llscan_ll()
|
||||||
|
{
|
||||||
|
local iface="$1"
|
||||||
|
|
||||||
|
printf "Scanning %s for IPv6 link-local neighbors ...\n\n" "$iface"
|
||||||
|
printf "\033[7m%-6s %s\033[0m\n" "#" "ADDRESS"
|
||||||
|
|
||||||
|
llping "$iface" -c3 -w3 2>/dev/null | awk '
|
||||||
|
/bytes from/ {
|
||||||
|
sub(/:$/, "", $4)
|
||||||
|
addr = $4
|
||||||
|
if (!seen[addr]++) {
|
||||||
|
printf "%-6d %s\n", ++n, addr
|
||||||
|
}
|
||||||
|
}
|
||||||
|
END {
|
||||||
|
printf "\n%d neighbor(s) found on '"$iface"'.\n", n+0
|
||||||
|
}
|
||||||
|
'
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user