Version 4 of this patch incorporates Inga's refinements, and a clear deliniation between methods to manage the local node key database, and the configuration clients calls to send those keys to remote node's configuration servers.
Brian Gix (1):
mesh: Add APIs for Provisioner and Config Client
doc/mesh-api.txt | 425 ++++++++++++++++++++++++++++++++++++++++++++++++++++---
1 file changed, 409 insertions(+), 16 deletions(-)
--
2.14.5
The added D-Bus APIs enable Applications to function in a Provisioner
Initiator role, and as a Configuration Client.
---
doc/mesh-api.txt | 425 ++++++++++++++++++++++++++++++++++++++++++++++++++++---
1 file changed, 409 insertions(+), 16 deletions(-)
diff --git a/doc/mesh-api.txt b/doc/mesh-api.txt
index 0b341a0f9..e4711292f 100644
--- a/doc/mesh-api.txt
+++ b/doc/mesh-api.txt
@@ -30,6 +30,7 @@ Methods:
org.bluez.mesh.Error.InvalidArguments
void Cancel(void)
+
Cancels an outstanding provisioning request initiated by Join()
method.
@@ -109,6 +110,37 @@ Methods:
PossibleErrors:
org.bluez.mesh.Error.NotFound
+ uint64 token CreateNetwork(object app_root, array{byte}[16] uuid)
+
+ This is the first method that an application calls to become
+ a Provisioner node, and a Configuration Client on a newly
+ created Mesh Network.
+
+ The app_root parameter is a D-Bus object root path of the
+ application that implements org.bluez.mesh.Application1
+ interface, and a org.bluez.mesh.Provisioner1 interface. The
+ application represents a node where child mesh elements have
+ their own objects that implement org.bluez.mesh.Element1
+ interface. The application hierarchy also contains a provision
+ agent object that implements org.bluez.mesh.ProvisionAgent1
+ interface. The standard DBus.ObjectManager interface must be
+ available on the app_root path.
+
+ The uuid parameter is a 16-byte array that contains Device UUID.
+
+ The returned token must be preserved by the application in
+ order to authenticate itself to the mesh daemon and attach to
+ the network as a mesh node by calling Attach() method or
+ permanently remove the identity of the mesh node by calling
+ Leave() method.
+
+ The other information the bluetooth-meshd daemon will preserve
+ about the initial node, is to give it the initial primary
+ unicast address (0x0001), and create and assign a net_key as the
+ primary network net_index (0x000).
+
+ PossibleErrors:
+ org.bluez.mesh.Error.InvalidArguments
Mesh Node Hierarchy
===================
@@ -146,6 +178,88 @@ Methods:
org.bluez.mesh.Error.InvalidArguments
org.bluez.mesh.Error.NotFound
+ void SendWithDeviceKey(object element_path, uint16 destination,
+ uint16 net_index, array{byte} data)
+
+ This method is used to send a message originated by a local
+ model encoded with the device key of the remote node.
+
+ The element_path parameter is the object path of an element from
+ a collection of the application elements (see Mesh Application
+ Hierarchy section).
+
+ The destination parameter contains the destination address. This
+ destination must be a uint16 to a unicast address, or a well
+ known group address.
+
+ The net_index parameter is the subnet index of the network on
+ which the message is to be sent.
+
+ The data parameter is an outgoing message to be encypted by the
+ meshd daemon and sent on.
+
+ Possible errors:
+ org.bluez.mesh.Error.InvalidArguments
+ org.bluez.mesh.Error.NotFound
+
+ void AddNetKey(object element_path, uint16 destination,
+ uint16 subnet_index, uint16 net_index, boolean update)
+
+ This method is used to send add or update network key originated
+ by the local configuration client to a remote configuration
+ server.
+
+ The element_path parameter is the object path of an element from
+ a collection of the application elements (see Mesh Application
+ Hierarchy section).
+
+ The destination parameter contains the destination address. This
+ destination must be a uint16 to a nodes primary unicast address.
+
+ The subnet_index parameter refers to the subnet index of the
+ network that is being added or updated. This key must exist in
+ the local key database.
+
+ The net_index parameter is the subnet index of the network on
+ which the message is to be sent.
+
+ The update parameter indicates if this is an addition or an
+ update. If true, the subnet key must be in the phase 1 state of
+ the key update procedure.
+
+ Possible errors:
+ org.bluez.mesh.Error.InvalidArguments
+ org.bluez.mesh.Error.NotFound
+
+ void AddAppKey(object element_path, uint16 destination,
+ uint16 app_index, uint16 net_index, boolean update)
+
+ This method is used to send add or update network key originated
+ by the local configuration client to a remote configuration
+ server.
+
+ The element_path parameter is the object path of an element from
+ a collection of the application elements (see Mesh Application
+ Hierarchy section).
+
+ The destination parameter contains the destination address. This
+ destination must be a uint16 to a nodes primary unicast address.
+
+ The app_index parameter refers to the application key which is
+ being added or updated. This key must exist in the local key
+ database.
+
+ The net_index parameter is the subnet index of the network on
+ which the message is to be sent.
+
+ The update parameter indicates if this is an addition or an
+ update. If true, the subnet key must be in the phase 1 state of
+ the key update procedure.
+
+ Possible errors:
+ org.bluez.mesh.Error.InvalidArguments
+ org.bluez.mesh.Error.NotFound
+
void Publish(object element_path, uint16 model, array{byte} data)
This method is used to send a publication originated by a local
@@ -193,6 +307,196 @@ Methods:
org.bluez.mesh.Error.DoesNotExist
org.bluez.mesh.Error.InvalidArguments
+Mesh Provisioning Hierarchy
+============================
+Service org.bluez.mesh
+Interface org.bluez.mesh.Management1
+Object path /org/bluez/mesh/node<xxxx>
+ where xxxx is a 4-digit hexadecimal number generated by daemon
+
+Methods:
+ void UnprovisionedScan(uint16 seconds)
+
+ This method is used by the application that supports
+ org.bluez.mesh.Provisioner1 interface to start listening
+ (scanning) for unprovisioned devices in the area. Scanning
+ will continue for the specified number of seconds, or, if 0 is
+ specified, then continuously until UnprovisionedScanCancel() is
+ called or if AddNode() method is called.
+
+ Each time a unique unprovisioned beacon is heard, the
+ ScanResult() method on the app will be called with the result.
+
+ PossibleErrors:
+ org.bluez.mesh.Error.NotAuthorized
+
+ void UnprovisionedScanCancel(void)
+
+ This method is used by the application that supports
+ org.bluez.mesh.Provisioner1 interface to stop listening
+ (scanning) for unprovisioned devices in the area.
+
+ PossibleErrors:
+ org.bluez.mesh.Error.NotAuthorized
+
+ void AddNode(array{byte}[16] uuid)
+
+ This method is used by the application that supports
+ org.bluez.mesh.Provisioner1 interface to add the
+ unprovisioned device specified by uuid, to the Network.
+
+ The uuid parameter is a 16-byte array that contains Device UUID
+ of the unprovisioned device to be added to the network.
+
+ PossibleErrors:
+ org.bluez.mesh.Error.InvalidArguments
+ org.bluez.mesh.Error.NotAuthorized
+
+ void CreateSubnet(uint16 net_index)
+
+ This method is used by the application that supports
+ org.bluez.mesh.Provisioner1 interface to generate and add a new
+ network subnet key.
+
+ The net_index parameter is a 12-bit value (0x001-0xFFF)
+ specifying which net key to add.
+
+ This call affects the local bluetooth-meshd key database only.
+
+ PossibleErrors:
+ org.bluez.mesh.Error.InvalidArguments
+ org.bluez.mesh.Error.NotAuthorized
+ org.bluez.mesh.Error.AlreadyExists
+
+ void UpdateSubnet(uint16 net_index)
+
+ This method is used by the application that supports
+ org.bluez.mesh.Provisioner1 interface to generate a new network
+ subnet key, and set it's key refresh state to Phase 1.
+
+ The net_index parameter is a 12-bit value (0x000-0xFFF)
+ specifying which net key to update. Note that the subnet must
+ exist prior to updating.
+
+ This call affects the local bluetooth-meshd key database only.
+
+ PossibleErrors:
+ org.bluez.mesh.Error.InvalidArguments
+ org.bluez.mesh.Error.NotAuthorized
+ org.bluez.mesh.Error.DoesNotExist
+
+ void DeleteSubnet(uint16 net_index)
+
+ This method is used by the application that supports
+ org.bluez.mesh.Provisioner1 interface to delete a subnet.
+
+ The net_index parameter is a 12-bit value (0x001-0xFFF)
+ specifying which net key to delete. The primary net key (0x000)
+ may not be deleted.
+
+ This call affects the local bluetooth-meshd key database only.
+
+ PossibleErrors:
+ org.bluez.mesh.Error.InvalidArguments
+ org.bluez.mesh.Error.NotAuthorized
+ org.bluez.mesh.Error.DoesNotExist
+
+ void SetKeyPhase(uint16 net_index, uint8 phase)
+ This method is used to set the master key update phase of the
+ given subnet.
+
+ The net_index parameter is a 12-bit value (0x000-0xFFF)
+ specifying which subnet phase to set.
+
+ The phase parameter is used to cycle the local key database
+ through the phases as defined by the Mesh Profile Specification.
+ Allowed values:
+ 0 - Cancel Key Refresh (May only be called from Phase 1,
+ and should never be called once the new key has
+ started propagating)
+ 1 - Invalid Argument (see NetKeyUpdate method)
+ 2 - Go to Phase 2 (May only be called from Phase 1)
+ 3 - Complete Key Refresh procedure (May only be called
+ from Phase 2)
+
+ This call affects the local bluetooth-meshd key database only.
+ It is the responsibility of the application to maintain the key
+ refresh phases per the Mesh Profile Specification.
+
+ PossibleErrors:
+ org.bluez.mesh.Error.InvalidArguments
+ org.bluez.mesh.Error.NotAuthorized
+ org.bluez.mesh.Error.DoesNotExist
+
+ void CreateAppKey(uint16 net_index, uint16 app_index)
+
+ This method is used by the application that supports
+ org.bluez.mesh.Provisioner1 interface to generate and add a new
+ application key.
+
+ The net_index parameter is a 12-bit value (0x000-0xFFF)
+ specifying which net key to bind the application key to.
+
+ The app_index parameter is a 12-bit value (0x000-0xFFF)
+ specifying which app key to add.
+
+ This call affects the local bluetooth-meshd key database only.
+
+ PossibleErrors:
+ org.bluez.mesh.Error.InvalidArguments
+ org.bluez.mesh.Error.NotAuthorized
+ org.bluez.mesh.Error.AlreadyExists
+
+ void UpdateAppKey(uint16 app_index)
+
+ This method is used by the application that supports
+ org.bluez.mesh.Provisioner1 interface to generate a new
+ application key.
+
+ The app_index parameter is a 12-bit value (0x000-0xFFF)
+ specifying which app key to update. Note that the subnet that
+ the key is bound to must exist and be in Phase 1.
+
+ This call affects the local bluetooth-meshd key database only.
+
+ PossibleErrors:
+ org.bluez.mesh.Error.InvalidArguments
+ org.bluez.mesh.Error.NotAuthorized
+ org.bluez.mesh.Error.DoesNotExist
+
+ void DeleteAppKey(uint16 app_index)
+
+ This method is used by the application that supports
+ org.bluez.mesh.Provisioner1 interface to delete an application
+ key.
+
+ The app_index parameter is a 12-bit value (0x000-0xFFF)
+ specifying which app key to delete.
+
+ This call affects the local bluetooth-meshd key database only.
+
+ PossibleErrors:
+ org.bluez.mesh.Error.InvalidArguments
+ org.bluez.mesh.Error.NotAuthorized
+ org.bluez.mesh.Error.DoesNotExist
+
+ void DeleteRemoteNode(uint16 primary, uint8 cnt)
+
+ This method is used by the application that supports
+ org.bluez.mesh.Provisioner1 interface to delete a remote node
+ from the local device key database.
+
+ The primary parameter specifies the unicast address of the
+ the node being deleted, and the number of elements that were
+ assigned to it.
+
+ This call affects the local bluetooth-meshd key database only.
+
+ PossibleErrors:
+ org.bluez.mesh.Error.InvalidArguments
+ org.bluez.mesh.Error.NotAuthorized
+ org.bluez.mesh.Error.DoesNotExist
+
Properties:
dict Features [read-only]
@@ -224,13 +528,24 @@ Properties:
This property indicates whether the periodic beaconing is
enabled (true) or disabled (false).
+ uint8 BeaconFlags [read-only]
+
+ This property may be read at any time to determine the flag
+ field setting on sent and received beacons of the primary
+ network key.
+
+ uint32 IvIndex [read-only]
+
+ This property may be read at any time to determine the IV_Index
+ that the current network is on. This information is only useful
+ for provisioning.
+
uint32 SecondsSinceLastHeard [read-only]
This property may be read at any time to determine the number of
seconds since mesh network layer traffic was last detected on
this node's network.
-
Mesh Application Hierarchy
==========================
Service unique name
@@ -247,6 +562,7 @@ An example mesh application hierarchy may look like this:
| - org.freedesktop.DBus.ObjectManager
| - org.bluez.mesh.Application1
| - org.bluez.mesh.Attention1 (optional)
+ | - org.bluez.mesh.Provisioner1 (optional,Provisioner)
|
-> /com/example/agent
| | - org.bluez.mesh.ProvisionAgent1
@@ -325,6 +641,22 @@ Methods:
The data parameter is the incoming message.
+ void DevKeyMessageReceived(uint16 source, uint16 net_index,
+ array{byte} data)
+
+ This method is called by meshd daemon when a message arrives
+ addressed to the application, which was sent with the remote
+ node's device key.
+
+ The source parameter is unicast address of the remote
+ node-element that sent the message.
+
+ The net_index parameter indicates what subnet the message was
+ received on, and if a response is required, the same subnet
+ must be used to send the response.
+
+ The data parameter is the incoming message.
+
void UpdateModelConfiguration(uint16 model_id, dict config)
This method is called by bluetooth-meshd daemon when a model's
@@ -383,7 +715,7 @@ Object path freely definable
This is an optional interface that implements health attention timer.
Methods:
- void SetTimer(uint8 element_index, uint16 time)
+ void SetTimer(uint8 element_index, uint16 time)
The element_index parameter is the element's index within the
node where the health server model is hosted.
@@ -406,6 +738,76 @@ Methods:
org.bluez.mesh.Error.NotSupported
+Mesh Provisioner Hierarchy
+============================
+Service unique name
+Interface org.bluez.mesh.Provisioner1
+Object path freely definable
+
+ ScanResult(int8 rssi, array{byte} data)
+
+ The method is called from the bluetooth-meshd daemon when a
+ unique UUID has been seen during UnprovisionedScan() for
+ unprovsioned devices.
+
+ The rssi parameter is a signed, normalized measurement of the
+ signal strength of the recieved unprovisioned beacon.
+
+ The data parameter is a variable length byte array, that may
+ have 1, 2 or 3 distinct fields contained in it including the 16
+ byte remote device UUID (always), a 32 bit mask of OOB
+ authentication flags (optional), and a 32 bit URI hash (if URI
+ bit set in OOB mask). Whether these fields exist or not is a
+ decision of the remote device.
+
+ If a beacon with a UUID that has already been reported is
+ recieved by the daemon, it will be silently discarded unless it
+ was recieved at a higher rssi power level.
+
+
+ uint16 net_index, uint8 flags, uint32 iv_index, uint16 unicast
+ RequestProvData()
+
+ This method is implemented by a Provisioner capable application
+ and is called when the remote device has been fully
+ authenticated and confirmed.
+
+ Return Parameters are from the Mesh Profile Spec:
+ net_index - Subnet index of the net_key
+ flags - Flags for IV_Update and Key Refresh
+ iv_index - Current IvIndex being used on the network
+ unicast - Primary Unicast address of the new node
+
+ PossibleErrors:
+ org.bluez.mesh.Error.Abort
+
+ void AddNodeComplete(array{byte}[16] uuid, uint16 unicast, uint8 count)
+
+ This method is called when the node provisioning initiated
+ by an AddNode() method call successfully completed.
+
+ The unicast parameter is the primary address that has been
+ assigned to the new node, and the address of it's config server.
+
+ The count parameter is the number of unicast addresses assigned
+ to the new node.
+
+ The new node may now be sent messages using the credentials
+ supplied by the RequestProvData method.
+
+ void AddNodeFailed(array{byte}[16] uuid, string reason)
+
+ This method is called when the node provisioning initiated by
+ AddNode() has failed. Depending on how far Provisioning
+ proceeded before failing, some cleanup of cached data may be
+ required.
+
+ The reason parameter identifies the reason for provisioning
+ failure. The defined values are: "aborted", "timeout",
+ "bad-pdu", "confirmation-failed", "out-of-resources",
+ "decryption-error", "unexpected-error",
+ "cannot-assign-addresses".
+
Provisioning Agent Hierarchy
============================
Service unique name
@@ -441,6 +843,7 @@ Methods:
the local role is Provisioner.
void DisplayString(string value)
+
This method is called when the Daemon has something important
for the Agent to Display, but does not require any additional
input locally. For instance: "Enter "ABCDE" on remote device".
@@ -451,9 +854,8 @@ Methods:
for the Agent to Display, but does not require any additional
input locally. For instance: "Enter 14939264 on remote device".
- The type parameter indicates the display method. Allowed values
+ The type parameter indicates the display method. Allowed values
are:
-
"blink" - Locally blink LED
"beep" - Locally make a noise
"vibrate" - Locally vibrate
@@ -466,12 +868,11 @@ Methods:
uint32 PromptNumeric(string type)
- This method is called when the Daemon has requires the user to
- enter a 1-99999999 digit decimal value.
+ This method is called when the Daemon requests the user to
+ enter a decimal value between 1-99999999.
The type parameter indicates the input method. Allowed values
are:
-
"blink" - Enter times remote LED blinked
"beep" - Enter times remote device beeped
"vibrate" - Enter times remote device vibrated
@@ -483,14 +884,13 @@ Methods:
This agent should prompt the user for specific input. For
instance: "Enter value being displayed by remote device".
- array{byte} PromptStatic(string type)
+ array{byte}[16] PromptStatic(string type)
This method is called when the Daemon requires a 16 octet byte
array, as an Out-of-Band authentication.
The type parameter indicates the input method. Allowed values
are:
-
"static-oob" - return 16 octet array
"in-alpha" - return 16 octet alpha array
@@ -511,7 +911,6 @@ Properties:
array{string} Capabilities [read-only]
An array of strings with the following allowed values:
-
"blink"
"beep"
"vibrate"
@@ -528,7 +927,6 @@ Properties:
Indicates availability of OOB data. An array of strings with the
following allowed values:
-
"other"
"uri"
"machine-code-2d"
@@ -546,8 +944,3 @@ Properties:
Uniform Resource Identifier points to out-of-band (OOB)
information (e.g., a public key)
-
-
-Mesh Provisioner Hierarchy
-==========================
-<TBD>
--
2.14.5
Hi Brian,
A couple of comments regarding device key management and local/remote
provisioning flow in general.
On 03/22, Brian Gix wrote:
> + uint64 token CreateNetwork(object app_root, array{byte}[16] uuid)
> +
> + This is the first method that an application calls to become
> + a Provisioner node, and a Configuration Client on a newly
> + created Mesh Network.
What if I would like my node to act as a Config Client without being a
provisioner? In our application, nodes can obtain network details from a
remote storage, so you don't need to be the one provisioning the network
in order to have access to device keys.
I think we should distinguish creating a node within meshd and
provisioning it, as there are a couple of ways to provision a new node:
- the node can advertise itself as unprovisioned device, waiting for
PB-ADV
- the node can advertise itself as a connectable, exposing PB-GATT
service
- the node might not want to advertise at all, and provision itself
"out of band", getting provisioning information from a remote
database
The 3rd option is of particular concern for new networks, and also
automatically adding devices to existing ones.
Because of that, I think we should rather split the
'org.bluez.mesh.Network1.Join' method into something like:
Mesh Network Hierarchy
======================
Service org.bluez.mesh
Interface org.bluez.mesh.Network1
Object path /org/bluez/mesh
Methods:
uint64 token
Create(array{byte}[16] uuid)
This is the first method that an application has to call to
become a provisioned node on a mesh network. The call will
allocate resources for the new node within the mesh daemon.
The token parameter serves as a unique identifier of the
particular node. The token must be preserved by the application
in order to authenticate itself to the mesh daemon and attach to
the network as a mesh node by calling Attach() method or
permanently remove the identity of the mesh node by calling
Remove() method.
The created node is initially idle, particularly it doesn't
initiate broadcasting of Unprovisioned Device Beacon.
(...)
void Remove(uint64 token)
This removes the configuration information about the mesh node
identified by the 64-bit token parameter. The token parameter
has been obtained as a result of successful Create() method call.
The application (after calling Attach) can then proceed to either
provision the node itself, or let it broadcast Unprovisioned Device
Beacons:
Mesh Node Hierarchy
===================
Service org.bluez.mesh
Interface org.bluez.mesh.Node1
void Provision(array{byte}[16] network_key, uint16_t address)
This call will add an unprovisioned local node to a given
network, using provided primary unicast address.
void Join(void)
The call will initiate broadcasting of Unprovisioned Device
Beacon.
If regular bluetoothd is available on the system bus, mesh
daemon will also register itself as a GATT application,
providing PB-GATT service.
void Cancel()
Cancels an outstanding provisioning request initiated by Join()
method.
After provisioning, the application will then receive freshly generate
device key, so that it can be stored externally.
Mesh Application Hierarchy
==========================
Service unique name
Interface org.bluez.mesh.Application1
Object path <app_defined_root>
void JoinComplete(array{byte}[16] device_key)
Having the application aware of the device key of a local node would
also mean that we could get rid of magic 0x7fff index for device key in
Send() call. Instead, we could provide two separate calls for
application-level and device-level security:
void SendWithApplicationKey(object element_path, uint16 destination,
uint16 key_index, array{byte} data)
void SendWithDeviceKey(object element_path, uint16 destination,
array{byte}[16] device_key, array{byte} data)
> Mesh Node Hierarchy
> ===================
> @@ -146,6 +178,88 @@ Methods:
> org.bluez.mesh.Error.InvalidArguments
> org.bluez.mesh.Error.NotFound
>
> + void SendWithDeviceKey(object element_path, uint16 destination,
> + uint16 net_index, array{byte} data)
> +
> + This method is used to send a message originated by a local
> + model encoded with the device key of the remote node.
> +
> + The element_path parameter is the object path of an element from
> + a collection of the application elements (see Mesh Application
> + Hierarchy section).
> +
> + The destination parameter contains the destination address. This
> + destination must be a uint16 to a unicast address, or a well
> + known group address.
I don't think it makes sense to send messages using device keys to
non-unicast addresses.
Also, since mesh daemon might not be the one who originally provisioned
device we would like to communicate with, I think this method should
accept the device key as an argument, moving key management duties to
the application.
Also, because it's not possible to configure publications using a device
key, I think device keys make sense only in case of unicast *requests*.
As far as I understand the mesh architecture, such responses are the
only case where we are receiving messages encrypted with remote node's
device key.
Therefore, I think the mesh daemon might want to retain the device key
passed to SendWithDeviceKey call for a certain time period, in order to
process these response.
This would also mean that this call:
> + void AddNode(array{byte}[16] uuid)
> +
> + This method is used by the application that supports
> + org.bluez.mesh.Provisioner1 interface to add the
> + unprovisioned device specified by uuid, to the Network.
> +
> + The uuid parameter is a 16-byte array that contains Device UUID
> + of the unprovisioned device to be added to the network.
should return generated device key back to the caller, so that it can
store the device key for later use, and/or send it to the remote
storage, so that other config clients can retrieve/use them.
All of that relieves the mesh daemon from keeping the network database,
and gives us certain symmetry in the API wrt local vs. remote nodes.
What do you think?
regards
--
Michał Lowas-Rzechonek <[email protected]>
Silvair http://silvair.com
Jasnogórska 44, 31-358 Krakow, POLAND
Hi Michal,
> From: Michał Lowas-Rzechonek [mailto:michal.lowas-
>
> A couple of comments regarding device key management and local/remote
> provisioning flow in general.
>
>
> On 03/22, Brian Gix wrote:
> > + uint64 token CreateNetwork(object app_root, array{byte}[16] uuid)
> > +
> > + This is the first method that an application calls to become
> > + a Provisioner node, and a Configuration Client on a newly
> > + created Mesh Network.
>
> What if I would like my node to act as a Config Client without being a
> provisioner? In our application, nodes can obtain network details from a
> remote storage, so you don't need to be the one provisioning the network in
> order to have access to device keys.
>
> I think we should distinguish creating a node within meshd and provisioning
> it, as there are a couple of ways to provision a new node:
>
> - the node can advertise itself as unprovisioned device, waiting for
> PB-ADV
> - the node can advertise itself as a connectable, exposing PB-GATT
> service
> - the node might not want to advertise at all, and provision itself
> "out of band", getting provisioning information from a remote
> database
>
> The 3rd option is of particular concern for new networks, and also
> automatically adding devices to existing ones.
>
> Because of that, I think we should rather split the
> 'org.bluez.mesh.Network1.Join' method into something like:
>
>
> Mesh Network Hierarchy
> ======================
> Service org.bluez.mesh
> Interface org.bluez.mesh.Network1
> Object path /org/bluez/mesh
>
> Methods:
> uint64 token
> Create(array{byte}[16] uuid)
>
> This is the first method that an application has to call to
> become a provisioned node on a mesh network. The call will
> allocate resources for the new node within the mesh daemon.
>
> The token parameter serves as a unique identifier of the
> particular node. The token must be preserved by the application
> in order to authenticate itself to the mesh daemon and attach to
> the network as a mesh node by calling Attach() method or
> permanently remove the identity of the mesh node by calling
> Remove() method.
>
> The created node is initially idle, particularly it doesn't
> initiate broadcasting of Unprovisioned Device Beacon.
>
> (...)
We are committed to using the org.freedesktop.DBus.ObjectManager in order to create the first Node of a new Mesh, which will require an object_path in all places where we have currently put them. This initial node must have at a minimum have Configuration Client available before it may Provision other nodes and start creating it's database of external nodes, by at a minimum requesting new node Composition data.
>
> void Remove(uint64 token)
>
> This removes the configuration information about the mesh node
> identified by the 64-bit token parameter. The token parameter
> has been obtained as a result of successful Create() method call.
>
>
> The application (after calling Attach) can then proceed to either provision the
> node itself, or let it broadcast Unprovisioned Device
> Beacons:
>
>
> Mesh Node Hierarchy
> ===================
> Service org.bluez.mesh
> Interface org.bluez.mesh.Node1
>
> void Provision(array{byte}[16] network_key, uint16_t address)
>
> This call will add an unprovisioned local node to a given
> network, using provided primary unicast address.
Internally, concern has been expressed for the security of all encryption keys used within Mesh. The current design avoids all handling of keys (Network, Application and Device keys) outside of the Daemon, particularly over the D-Bus interface. It is true that we will need to eventually be able to Export Key Databases such that Provisioning (and Configuration Client) responsibilities can be either Transferred or Shared. We currently envision this as a privileged operation where an Export tool of some sort can retrieve the keys it needs over a mechanism the is *not* D-Bus.... Perhaps either a file descriptor or a privileged pipe. Initially of course, anyone with Root privileges is able to retrieve the keys from within the Daemon's storage structure.
> void Join(void)
>
> The call will initiate broadcasting of Unprovisioned Device
> Beacon.
>
> If regular bluetoothd is available on the system bus, mesh
> daemon will also register itself as a GATT application,
> providing PB-GATT service.
This will again need to support the org.freedesktop.DBus.ObjectManager interface. While it may be an open question as to whether to include the UUID as a parameter here, or as a Property of the Application creating the node, we chose parameter, since it is information that is typically needed only at Provisioning, and such will not need to be re-quired over the life of the node via a Property.
Also note, that the ObjectManager interface is required so that the number of Elements are available to the Provisioning Procedure, and so that upon successful completion of the Provisioning procedure, all Configuration Server requests can be immediately serviced by the daemon. It is the goal of the daemon to provide all Foundational Server functionality (server functionality that requires the Device Key of the node) with or without a foreground Application "Attached" to the node.
We also chose to put Join/Cancel/Leave/Create in the Network1 interface because no "Node" technically exists until provisioning is successful. The methods under the Node1 interface are only available to applications which have "Attached" using a token.
>
> void Cancel()
>
> Cancels an outstanding provisioning request initiated by Join()
> method.
>
>
> After provisioning, the application will then receive freshly generate device
> key, so that it can be stored externally.
See earlier comment about keeping keys internally secured.
>
>
> Mesh Application Hierarchy
> ==========================
> Service unique name
> Interface org.bluez.mesh.Application1
> Object path <app_defined_root>
>
> void JoinComplete(array{byte}[16] device_key)
>
>
> Having the application aware of the device key of a local node would also
> mean that we could get rid of magic 0x7fff index for device key in
> Send() call. Instead, we could provide two separate calls for application-level
> and device-level security:
>
> void SendWithApplicationKey(object element_path, uint16 destination,
> uint16 key_index, array{byte} data)
>
> void SendWithDeviceKey(object element_path, uint16 destination,
> array{byte}[16] device_key,
> array{byte} data)
>
> > Mesh Node Hierarchy
> > ===================
> > @@ -146,6 +178,88 @@ Methods:
> > org.bluez.mesh.Error.InvalidArguments
> > org.bluez.mesh.Error.NotFound
> >
> > + void SendWithDeviceKey(object element_path, uint16 destination,
> > + uint16 net_index, array{byte} data)
> > +
> > + This method is used to send a message originated by a local
> > + model encoded with the device key of the remote node.
> > +
> > + The element_path parameter is the object path of an
> element from
> > + a collection of the application elements (see Mesh
> Application
> > + Hierarchy section).
> > +
> > + The destination parameter contains the destination address.
> This
> > + destination must be a uint16 to a unicast address, or a well
> > + known group address.
>
> I don't think it makes sense to send messages using device keys to non-
> unicast addresses.
I don't think this is 100% certain... An "All Provisioners" or "All Config Clients" is a distinct possibility, especially given that Provisioning and Config Client duties may at some point be shared. It is not 100% certain that a Node will *always* know who the active Provisioner is, and whether a Config Server will ever need to send unsolicited reports to a Provisioner (if for instance a node is going to permanently leave). This is forward looking, and in any case, uint16 is the smallest reasonable address space for unicast addresses, and so we are choosing to use the more generic "destination" for now.
> Also, since mesh daemon might not be the one who originally provisioned
> device we would like to communicate with, I think this method should accept
> the device key as an argument, moving key management duties to the
> application.
>
> Also, because it's not possible to configure publications using a device key, I
> think device keys make sense only in case of unicast *requests*.
> As far as I understand the mesh architecture, such responses are the only
> case where we are receiving messages encrypted with remote node's device
> key.
>
> Therefore, I think the mesh daemon might want to retain the device key
> passed to SendWithDeviceKey call for a certain time period, in order to
> process these response.
This has been part of the conversation. Marcel was the main advocate/instigator for *not* passing the device key here, and he made a compelling argument for the proper securing of *all* keys (not just the Device Key).
> This would also mean that this call:
>
> > + void AddNode(array{byte}[16] uuid)
> > +
> > + This method is used by the application that supports
> > + org.bluez.mesh.Provisioner1 interface to add the
> > + unprovisioned device specified by uuid, to the Network.
> > +
> > + The uuid parameter is a 16-byte array that contains Device
> UUID
> > + of the unprovisioned device to be added to the network.
>
> should return generated device key back to the caller, so that it can store the
> device key for later use, and/or send it to the remote storage, so that other
> config clients can retrieve/use them.
>
> All of that relieves the mesh daemon from keeping the network database,
> and gives us certain symmetry in the API wrt local vs. remote nodes.
>
> What do you think?
Most of your recommendation directly affect the security of the Keys, and whether they get passed via D-Bus... And this is something that I would *not* do without the OK from Marcel Holtmann, and I am inclined to believe this assent will probably not happen, for reasons stated, and that I agree with.
However, a secure methodology for Exporting keys should definitely be high on our TODO list.
>
> regards
> --
> Michał Lowas-Rzechonek <[email protected]>
> Silvair http://silvair.com
> Jasnogórska 44, 31-358 Krakow, POLAND
BR,
Brian Gix
Hi Brian,
Thanks for the response.
On 04/03, Gix, Brian wrote:
> > Service org.bluez.mesh
> > Interface org.bluez.mesh.Network1
> >
> > uint64 token
> > Create(array{byte}[16] uuid)
>
> We are committed to using the org.freedesktop.DBus.ObjectManager in
> order to create the first Node of a new Mesh, which will require an
> object_path in all places where we have currently put them. This
> initial node must have at a minimum have Configuration Client
> available before it may Provision other nodes and start creating it's
> database of external nodes, by at a minimum requesting new node
> Composition data.
>
> (...)
>
> We also chose to put Join/Cancel/Leave/Create in the Network1
> interface because no "Node" technically exists until provisioning is
> successful. The methods under the Node1 interface are only available
> to applications which have "Attached" using a token.
Ok, but this part of the proposed API is about creating an unprovisioned
node, so object_path is not needed yet.
That being said, you're right that path to the application 'owning' the
node should indeed be mandatory for Provision() and Join() calls, or at
least the application should be required to Attach() itself before
calling these functions.
Main goal in the proposal was splitting node creation into two parts:
registering a node within the daemon, and provisioning it. In the
current API, node starts broadcasting unprovisioned device beacons as
soon as it's created. There are use cases where this is not desirable.
Imagine an existing network provisioned by someone else, for example a
cloud-based application communicating with mesh via a mobile device.
We would like to add a Linux-based device to that network. Since at the
moment it's not possible to provision meshd via PB-GATT (for various
reasons), the most practical option would be to 'self-provision', by
retrieving network keys/address from cloud-based provisioner, and
sending a generated device key back to the cloud, so that freshly
provisioned Linux box can be later managed in the same was as all other
nodes.
To achieve that, we need some kind of API that allows provisioning meshd
locally, ideally via D-Bus.
Another interesting option to achieve that would be to use meshd as
provider of PB-ADV bearer, and do the provisioning dance in the
application. This seems doable, but complicated.
I think meshd should not assume that it's the sole 'owner' of the
network; such an approach severely limits its use cases. As another
example, application keys also may be submitted from the outside,
instead of generating them within the daemon.
> Internally, concern has been expressed for the security of all
> encryption keys used within Mesh. The current design avoids all
> handling of keys (Network, Application and Device keys) outside of the
> Daemon, particularly over the D-Bus interface.
Hm, may I ask why 'particularly over D-Bus'? As far as I understand, the
bus is secure - you cannot eavesdrop messages without proper privileges,
and the daemon authenticates client applications.
Services like NetworkManager doesn't seem to have a problem with
transferring secrets over D-Bus.
Moreover, since the Attach() token already travels through the bus, if a
3rd party can intercept it, they don't even need to extract keys from
the daemon - they can just use the regular API to communicate with the
mesh.
> > I don't think it makes sense to send messages using device keys to non-
> > unicast addresses.
>
> I don't think this is 100% certain...
The spec explicitly forbids using device key with non-unicast addresses.
See Mesh Profile v1.0.1, section 3.4.3 Address validity, table 3.6.
> Most of your recommendation directly affect the security of the Keys,
> and whether they get passed via D-Bus... And this is something that I
> would *not* do without the OK from Marcel Holtmann, and I am inclined
> to believe this assent will probably not happen, for reasons stated,
> and that I agree with.
Could you please point me to the relevant thread? I know I'm late to the
party and would very much like to catch up, as much as I can.
cheers
--
Michał Lowas-Rzechonek <[email protected]>
Silvair http://silvair.com
Jasnogórska 44, 31-358 Krakow, POLAND
Hi,
On 04/05, Stotland, Inga wrote:
> > Imagine an existing network provisioned by someone else, (...)
> > We would like to add a Linux-based device to that network. (...)
> > To achieve that, we need some kind of API that allows provisioning
> > meshd locally, ideally via D-Bus.
>
> To me, this scenario seems to me more like "import the node" rather
> than "provision the device".
Yes, indeed. Having an API to import a complete node into the daemon
would be enough. But note that such an import would need to contain, and
*all* the keys, device key included.
> We could do that with either a new Import(dict cofiguration) method,
> where configuration is a dictionary that contains all the preset
> values (netkeys, uuid, unicast, etc). This method is different from
> Create() since the netkeys are coming from the "outside".
Yes, that would do, but there seem to be some objections to passing keys
over DBus.
> The current approach is to keep all the netkeys and appkeys within the
> daemon without ever exposing them to an application.
Yeah, I know. I just don't think it's practical to assume this, because
of the use cases mentioned earlier in this thread. I strongly feel there
is a need for a centralized database, handled outside of the daemon,
ideally on a some kind of internet server.
> Alternatively, If a node has been created and configured by a third
> party then adding it to the existing meshd management could be
> achieved by transcribing its configuration into a meshd internal
> storage format (which is in json readable form and could be
> potentially exposed as a schema).
That's true, but this would also mean that the storage format becomes an
API.
I don't think this is a good idea, I'd rather allow the daemon to modify
storage format freely (with a proper migration from older versions, of
course). Especially considering that while JSON works for now, I think
in a long run node data should be stored in some kind of transactional
database.
> > Moreover, since the Attach() token already travels through the bus (...)
> Just having the token is not enough: there are some checking
> mechanisms (and I believe they need to be extended) to check the
> validity of the attaching application.
As far as I can see, these checks mostly concern composition data
(elements/models in particular), and this can be easily queried. Mesh
daemon doesn't seem to use any additional secrets besides the token.
So from what I gather, the daemon already depends on the bus being
secure, and I don't see any practical difference between exchange of
tokens and exchange of keys.
> > > Most of your recommendation directly affect the security of the Keys,
> > > and whether they get passed via D-Bus... And this is something that I
> > > would *not* do without the OK from Marcel Holtmann (...)
> > Could you please point me to the relevant thread? I know I'm late to
> > he party and would very much like to catch up, as much as I can.
> This was a verbal discussion I believe.
Oh... Could someone (Brian? Marcel?) please summarize the main points
then? It's hard to argue about arguments I cannot possibly know :(
regards
--
Michał Lowas-Rzechonek <[email protected]>
Silvair http://silvair.com
Jasnogórska 44, 31-358 Krakow, POLAND
Hi Michal,
> From: Michal Lowas-Rzechonek
>
> On 04/05, Stotland, Inga wrote:
> > > Imagine an existing network provisioned by someone else, (...) We
> > > would like to add a Linux-based device to that network. (...) To
> > > achieve that, we need some kind of API that allows provisioning
> > > meshd locally, ideally via D-Bus.
> >
> > To me, this scenario seems to me more like "import the node" rather
> > than "provision the device".
>
> Yes, indeed. Having an API to import a complete node into the daemon
> would be enough. But note that such an import would need to contain, and
> *all* the keys, device key included.
I think this is the conclusion we are coming to as well. An import would be a "Third Way" (after "CreateNetwork" and "Join") to bring a node into existence, and it would be created basically from "All information currently stored in the node.json file" except for the token, and the internal object path, which would be generated in the daemon to ensure no collisions. In addition to the keys, I think we will need all of the bindings/pub/sub/seq/iv_index/kr/features/unicast/uuid as well, since the understanding here will be that this node already exists on the network, and thus all of this information will be assumed valid in any copies of a remote Config Clients full mesh Configuration database.
>
> > We could do that with either a new Import(dict cofiguration) method,
> > where configuration is a dictionary that contains all the preset
> > values (netkeys, uuid, unicast, etc). This method is different from
> > Create() since the netkeys are coming from the "outside".
>
> Yes, that would do, but there seem to be some objections to passing keys
> over DBus.
The objection is more to the daemon exposing to the application keys under the daemon's control, as a matter of normal operation. An Import operation, on the other hand, is an application passing keys *to* the daemon, requesting that they be made into a locally managed node.
>
> > The current approach is to keep all the netkeys and appkeys within the
> > daemon without ever exposing them to an application.
>
> Yeah, I know. I just don't think it's practical to assume this, because of the
> use cases mentioned earlier in this thread. I strongly feel there is a need for a
> centralized database, handled outside of the daemon, ideally on a some kind
> of internet server.
I don't think anyone is rejecting the idea that export/import needs to be supported in some fashion... Just that we need to recognize that:
1. The daemon needs permanent access to all keys at all times internally for all nodes it is handling
2. Importing/Exporting will be an "Exceptional Case" which will be needed, but rarely.
>
> > Alternatively, If a node has been created and configured by a third
> > party then adding it to the existing meshd management could be
> > achieved by transcribing its configuration into a meshd internal
> > storage format (which is in json readable form and could be
> > potentially exposed as a schema).
>
> That's true, but this would also mean that the storage format becomes an
> API.
>
> I don't think this is a good idea, I'd rather allow the daemon to modify storage
> format freely (with a proper migration from older versions, of course).
> Especially considering that while JSON works for now, I think in a long run
> node data should be stored in some kind of transactional database.
I also agree that the "Import API" should be generic enough so that *how* the daemon chooses to maintain it's internal databases can be entirely hidden from the applications using the d-bus APIs.
I have no opinion on whether this "Import API" uses D-Bus dictionaries, or a well-documented JSON schema.
>
> > > Moreover, since the Attach() token already travels through the bus
> > > (...)
> > Just having the token is not enough: there are some checking
> > mechanisms (and I believe they need to be extended) to check the
> > validity of the attaching application.
>
> As far as I can see, these checks mostly concern composition data
> (elements/models in particular), and this can be easily queried. Mesh
> daemon doesn't seem to use any additional secrets besides the token.
This is a fair and correct statement.
>
> So from what I gather, the daemon already depends on the bus being
> secure, and I don't see any practical difference between exchange of tokens
> and exchange of keys.
The theory of operation here is that once a node is formed, the only data that needs to be stored by the application is the token. The application knows none of their application, network, or device keys (an import application aside). So if an Application is compromised, the token will allow an attacker to masquerade as the compromised node/application, but they will still not have access to network keys, and will not be able to masquerade as the Config Server of the compromised node.
The breach will be contained at the access layer of the compromised node.
>
> > > > Most of your recommendation directly affect the security of the
> > > > Keys, and whether they get passed via D-Bus... And this is
> > > > something that I would *not* do without the OK from Marcel
> > > > Holtmann (...)
> > > Could you please point me to the relevant thread? I know I'm late to
> > > he party and would very much like to catch up, as much as I can.
> > This was a verbal discussion I believe.
>
> Oh... Could someone (Brian? Marcel?) please summarize the main points
> then? It's hard to argue about arguments I cannot possibly know :(
Without defining what kind of underlying "Key Chain" or "Key Ring" security mechanisms that might be available to the daemon, the general idea is that under normal operating conditions, the "Keeper of the Keys" is always the Daemon. The daemon already requires access to all of the keys. And allowing average Applications to *also* have access to the keys fundamentally reduces security by increasing the number of key copies that must be maintained, and by putting the level of local key security outside the direct control of the daemon.
BR,
Brian Gix