Implement end-to-end app payload encryption.

This implements end-to-end encryption between the end-device and
end-application. The encrypted AppSKey or SessionKeyID is forwarded to
the end-application which should be able to decrypt or request the
AppSKey to decrypt the uplink payload. As well the end-application will
be able to enqueue encrypted application payloads.

Using this mechanism, ChirpStack will never have access to the uplink
and downlink application-payloads.
This commit is contained in:
Orne Brocaar
2023-10-05 13:05:53 +01:00
parent 503beaa2fd
commit 41d00cb651
49 changed files with 4859 additions and 783 deletions

File diff suppressed because one or more lines are too long

View File

@ -315,7 +315,7 @@ class GetDeviceLinkMetricsResponse(_message.Message):
def __init__(self, rx_packets: _Optional[_Union[_common_pb2.Metric, _Mapping]] = ..., gw_rssi: _Optional[_Union[_common_pb2.Metric, _Mapping]] = ..., gw_snr: _Optional[_Union[_common_pb2.Metric, _Mapping]] = ..., rx_packets_per_freq: _Optional[_Union[_common_pb2.Metric, _Mapping]] = ..., rx_packets_per_dr: _Optional[_Union[_common_pb2.Metric, _Mapping]] = ..., errors: _Optional[_Union[_common_pb2.Metric, _Mapping]] = ...) -> None: ...
class DeviceQueueItem(_message.Message):
__slots__ = ["id", "dev_eui", "confirmed", "f_port", "data", "object", "is_pending", "f_cnt_down"]
__slots__ = ["id", "dev_eui", "confirmed", "f_port", "data", "object", "is_pending", "f_cnt_down", "is_encrypted"]
ID_FIELD_NUMBER: _ClassVar[int]
DEV_EUI_FIELD_NUMBER: _ClassVar[int]
CONFIRMED_FIELD_NUMBER: _ClassVar[int]
@ -324,6 +324,7 @@ class DeviceQueueItem(_message.Message):
OBJECT_FIELD_NUMBER: _ClassVar[int]
IS_PENDING_FIELD_NUMBER: _ClassVar[int]
F_CNT_DOWN_FIELD_NUMBER: _ClassVar[int]
IS_ENCRYPTED_FIELD_NUMBER: _ClassVar[int]
id: str
dev_eui: str
confirmed: bool
@ -332,7 +333,8 @@ class DeviceQueueItem(_message.Message):
object: _struct_pb2.Struct
is_pending: bool
f_cnt_down: int
def __init__(self, id: _Optional[str] = ..., dev_eui: _Optional[str] = ..., confirmed: bool = ..., f_port: _Optional[int] = ..., data: _Optional[bytes] = ..., object: _Optional[_Union[_struct_pb2.Struct, _Mapping]] = ..., is_pending: bool = ..., f_cnt_down: _Optional[int] = ...) -> None: ...
is_encrypted: bool
def __init__(self, id: _Optional[str] = ..., dev_eui: _Optional[str] = ..., confirmed: bool = ..., f_port: _Optional[int] = ..., data: _Optional[bytes] = ..., object: _Optional[_Union[_struct_pb2.Struct, _Mapping]] = ..., is_pending: bool = ..., f_cnt_down: _Optional[int] = ..., is_encrypted: bool = ...) -> None: ...
class EnqueueDeviceQueueItemRequest(_message.Message):
__slots__ = ["queue_item"]
@ -373,3 +375,15 @@ class FlushDevNoncesRequest(_message.Message):
DEV_EUI_FIELD_NUMBER: _ClassVar[int]
dev_eui: str
def __init__(self, dev_eui: _Optional[str] = ...) -> None: ...
class GetDeviceNextFCntDownRequest(_message.Message):
__slots__ = ["dev_eui"]
DEV_EUI_FIELD_NUMBER: _ClassVar[int]
dev_eui: str
def __init__(self, dev_eui: _Optional[str] = ...) -> None: ...
class GetDeviceNextFCntDownResponse(_message.Message):
__slots__ = ["f_cnt_down"]
F_CNT_DOWN_FIELD_NUMBER: _ClassVar[int]
f_cnt_down: int
def __init__(self, f_cnt_down: _Optional[int] = ...) -> None: ...

View File

@ -111,6 +111,11 @@ class DeviceServiceStub(object):
request_serializer=chirpstack__api_dot_api_dot_device__pb2.GetDeviceQueueItemsRequest.SerializeToString,
response_deserializer=chirpstack__api_dot_api_dot_device__pb2.GetDeviceQueueItemsResponse.FromString,
)
self.GetNextFCntDown = channel.unary_unary(
'/api.DeviceService/GetNextFCntDown',
request_serializer=chirpstack__api_dot_api_dot_device__pb2.GetDeviceNextFCntDownRequest.SerializeToString,
response_deserializer=chirpstack__api_dot_api_dot_device__pb2.GetDeviceNextFCntDownResponse.FromString,
)
class DeviceServiceServicer(object):
@ -256,6 +261,15 @@ class DeviceServiceServicer(object):
context.set_details('Method not implemented!')
raise NotImplementedError('Method not implemented!')
def GetNextFCntDown(self, request, context):
"""GetNextFCntDown returns the next FCntDown to use for enqueing encrypted
downlinks. The difference with the DeviceActivation f_cont_down is that
this method takes potential existing queue-items into account.
"""
context.set_code(grpc.StatusCode.UNIMPLEMENTED)
context.set_details('Method not implemented!')
raise NotImplementedError('Method not implemented!')
def add_DeviceServiceServicer_to_server(servicer, server):
rpc_method_handlers = {
@ -354,6 +368,11 @@ def add_DeviceServiceServicer_to_server(servicer, server):
request_deserializer=chirpstack__api_dot_api_dot_device__pb2.GetDeviceQueueItemsRequest.FromString,
response_serializer=chirpstack__api_dot_api_dot_device__pb2.GetDeviceQueueItemsResponse.SerializeToString,
),
'GetNextFCntDown': grpc.unary_unary_rpc_method_handler(
servicer.GetNextFCntDown,
request_deserializer=chirpstack__api_dot_api_dot_device__pb2.GetDeviceNextFCntDownRequest.FromString,
response_serializer=chirpstack__api_dot_api_dot_device__pb2.GetDeviceNextFCntDownResponse.SerializeToString,
),
}
generic_handler = grpc.method_handlers_generic_handler(
'api.DeviceService', rpc_method_handlers)
@ -687,3 +706,20 @@ class DeviceService(object):
chirpstack__api_dot_api_dot_device__pb2.GetDeviceQueueItemsResponse.FromString,
options, channel_credentials,
insecure, call_credentials, compression, wait_for_ready, timeout, metadata)
@staticmethod
def GetNextFCntDown(request,
target,
options=(),
channel_credentials=None,
call_credentials=None,
insecure=False,
compression=None,
wait_for_ready=None,
timeout=None,
metadata=None):
return grpc.experimental.unary_unary(request, target, '/api.DeviceService/GetNextFCntDown',
chirpstack__api_dot_api_dot_device__pb2.GetDeviceNextFCntDownRequest.SerializeToString,
chirpstack__api_dot_api_dot_device__pb2.GetDeviceNextFCntDownResponse.FromString,
options, channel_credentials,
insecure, call_credentials, compression, wait_for_ready, timeout, metadata)

File diff suppressed because one or more lines are too long

View File

@ -28,6 +28,7 @@ class LogCode(int, metaclass=_enum_type_wrapper.EnumTypeWrapper):
UPLINK_F_CNT_RETRANSMISSION: _ClassVar[LogCode]
DOWNLINK_GATEWAY: _ClassVar[LogCode]
RELAY_NEW_END_DEVICE: _ClassVar[LogCode]
F_CNT_DOWN: _ClassVar[LogCode]
INFO: LogLevel
WARNING: LogLevel
ERROR: LogLevel
@ -41,6 +42,7 @@ UPLINK_MIC: LogCode
UPLINK_F_CNT_RETRANSMISSION: LogCode
DOWNLINK_GATEWAY: LogCode
RELAY_NEW_END_DEVICE: LogCode
F_CNT_DOWN: LogCode
class DeviceInfo(_message.Message):
__slots__ = ["tenant_id", "tenant_name", "application_id", "application_name", "device_profile_id", "device_profile_name", "device_name", "dev_eui", "device_class_enabled", "tags"]
@ -89,8 +91,16 @@ class UplinkRelayRxInfo(_message.Message):
wor_channel: int
def __init__(self, dev_eui: _Optional[str] = ..., frequency: _Optional[int] = ..., dr: _Optional[int] = ..., snr: _Optional[int] = ..., rssi: _Optional[int] = ..., wor_channel: _Optional[int] = ...) -> None: ...
class JoinServerContext(_message.Message):
__slots__ = ["session_key_id", "app_s_key"]
SESSION_KEY_ID_FIELD_NUMBER: _ClassVar[int]
APP_S_KEY_FIELD_NUMBER: _ClassVar[int]
session_key_id: str
app_s_key: _common_pb2.KeyEnvelope
def __init__(self, session_key_id: _Optional[str] = ..., app_s_key: _Optional[_Union[_common_pb2.KeyEnvelope, _Mapping]] = ...) -> None: ...
class UplinkEvent(_message.Message):
__slots__ = ["deduplication_id", "time", "device_info", "dev_addr", "adr", "dr", "f_cnt", "f_port", "confirmed", "data", "object", "rx_info", "tx_info", "relay_rx_info"]
__slots__ = ["deduplication_id", "time", "device_info", "dev_addr", "adr", "dr", "f_cnt", "f_port", "confirmed", "data", "object", "rx_info", "tx_info", "relay_rx_info", "join_server_context"]
DEDUPLICATION_ID_FIELD_NUMBER: _ClassVar[int]
TIME_FIELD_NUMBER: _ClassVar[int]
DEVICE_INFO_FIELD_NUMBER: _ClassVar[int]
@ -105,6 +115,7 @@ class UplinkEvent(_message.Message):
RX_INFO_FIELD_NUMBER: _ClassVar[int]
TX_INFO_FIELD_NUMBER: _ClassVar[int]
RELAY_RX_INFO_FIELD_NUMBER: _ClassVar[int]
JOIN_SERVER_CONTEXT_FIELD_NUMBER: _ClassVar[int]
deduplication_id: str
time: _timestamp_pb2.Timestamp
device_info: DeviceInfo
@ -119,21 +130,24 @@ class UplinkEvent(_message.Message):
rx_info: _containers.RepeatedCompositeFieldContainer[_gw_pb2.UplinkRxInfo]
tx_info: _gw_pb2.UplinkTxInfo
relay_rx_info: UplinkRelayRxInfo
def __init__(self, deduplication_id: _Optional[str] = ..., time: _Optional[_Union[_timestamp_pb2.Timestamp, _Mapping]] = ..., device_info: _Optional[_Union[DeviceInfo, _Mapping]] = ..., dev_addr: _Optional[str] = ..., adr: bool = ..., dr: _Optional[int] = ..., f_cnt: _Optional[int] = ..., f_port: _Optional[int] = ..., confirmed: bool = ..., data: _Optional[bytes] = ..., object: _Optional[_Union[_struct_pb2.Struct, _Mapping]] = ..., rx_info: _Optional[_Iterable[_Union[_gw_pb2.UplinkRxInfo, _Mapping]]] = ..., tx_info: _Optional[_Union[_gw_pb2.UplinkTxInfo, _Mapping]] = ..., relay_rx_info: _Optional[_Union[UplinkRelayRxInfo, _Mapping]] = ...) -> None: ...
join_server_context: JoinServerContext
def __init__(self, deduplication_id: _Optional[str] = ..., time: _Optional[_Union[_timestamp_pb2.Timestamp, _Mapping]] = ..., device_info: _Optional[_Union[DeviceInfo, _Mapping]] = ..., dev_addr: _Optional[str] = ..., adr: bool = ..., dr: _Optional[int] = ..., f_cnt: _Optional[int] = ..., f_port: _Optional[int] = ..., confirmed: bool = ..., data: _Optional[bytes] = ..., object: _Optional[_Union[_struct_pb2.Struct, _Mapping]] = ..., rx_info: _Optional[_Iterable[_Union[_gw_pb2.UplinkRxInfo, _Mapping]]] = ..., tx_info: _Optional[_Union[_gw_pb2.UplinkTxInfo, _Mapping]] = ..., relay_rx_info: _Optional[_Union[UplinkRelayRxInfo, _Mapping]] = ..., join_server_context: _Optional[_Union[JoinServerContext, _Mapping]] = ...) -> None: ...
class JoinEvent(_message.Message):
__slots__ = ["deduplication_id", "time", "device_info", "dev_addr", "relay_rx_info"]
__slots__ = ["deduplication_id", "time", "device_info", "dev_addr", "relay_rx_info", "join_server_context"]
DEDUPLICATION_ID_FIELD_NUMBER: _ClassVar[int]
TIME_FIELD_NUMBER: _ClassVar[int]
DEVICE_INFO_FIELD_NUMBER: _ClassVar[int]
DEV_ADDR_FIELD_NUMBER: _ClassVar[int]
RELAY_RX_INFO_FIELD_NUMBER: _ClassVar[int]
JOIN_SERVER_CONTEXT_FIELD_NUMBER: _ClassVar[int]
deduplication_id: str
time: _timestamp_pb2.Timestamp
device_info: DeviceInfo
dev_addr: str
relay_rx_info: UplinkRelayRxInfo
def __init__(self, deduplication_id: _Optional[str] = ..., time: _Optional[_Union[_timestamp_pb2.Timestamp, _Mapping]] = ..., device_info: _Optional[_Union[DeviceInfo, _Mapping]] = ..., dev_addr: _Optional[str] = ..., relay_rx_info: _Optional[_Union[UplinkRelayRxInfo, _Mapping]] = ...) -> None: ...
join_server_context: JoinServerContext
def __init__(self, deduplication_id: _Optional[str] = ..., time: _Optional[_Union[_timestamp_pb2.Timestamp, _Mapping]] = ..., device_info: _Optional[_Union[DeviceInfo, _Mapping]] = ..., dev_addr: _Optional[str] = ..., relay_rx_info: _Optional[_Union[UplinkRelayRxInfo, _Mapping]] = ..., join_server_context: _Optional[_Union[JoinServerContext, _Mapping]] = ...) -> None: ...
class AckEvent(_message.Message):
__slots__ = ["deduplication_id", "time", "device_info", "queue_item_id", "acknowledged", "f_cnt_down"]