Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

API SecureManager::SendMessage, use SecureSessoinHandle instead NodeId #3728

Merged
merged 1 commit into from
Jan 12, 2021

Conversation

kghost
Copy link
Contributor

@kghost kghost commented Nov 9, 2020

Problem

Currently SecureSessionMgrBase::SendMessage takes a PeerNodeId as key to find proper PeerConnectionState, but is way may causing unexpected behavior. For example.

  • If there are multiple connection to the same node, there is no way to specify which connection to use,
  • When switching a connection (SPAKE -> SIGMA, or SIGMA -> SPAKE), the application can not aware the change, and mistakenly send messages to wrong channel.
  • The ACL permission information is associated with a certification, which is used to initialized the connection, the connection derives access permission from it. So we should use connection as key to verify permission. Using node id is insecure and may cause potential security flaws.

Summary of Changes

  1. SecureSessionMgrBase::SendMessage take a Transport::PeerConnectionState * instead of PeerNodeId
  2. Enable SecureSessionMgrDelegate::OnNewConnection so that upper components can receive a PeerConnectionState object
  3. Add SecureSessionMgrDelegate::OnConnectionExpired callback, so upper components can acknowledge that the connection has been released.

@kghost
Copy link
Contributor Author

kghost commented Nov 9, 2020

Before I resolve all CI failures, I want to collect opinions first.

@andy31415
Copy link
Contributor

I have conmcerns about the approach: I agree with the intent that there may be more communication paths than 1 to a node, which we plan to call 'exchanges'. The PeerConnectionState however was meant to encapsulate things like encryption keys and counters, which applications should not see.

I would rather imagine we would like something like a 'connectionID' exposed that could be something like a pair<nodeid, exchangeid>. This sounds like a minimal information required rather than an entire large structure pointer.

@andy31415
Copy link
Contributor

I have conmcerns about the approach: I agree with the intent that there may be more communication paths than 1 to a node, which we plan to call 'exchanges'. The PeerConnectionState however was meant to encapsulate things like encryption keys and counters, which applications should not see.

I would rather imagine we would like something like a 'connectionID' exposed that could be something like a pair<nodeid, exchangeid>. This sounds like a minimal information required rather than an entire large structure pointer.

Marked as 'changes requested' since I believe the PeerConnectionState to be too large of an object to pass around and ideally API boundaries should be with smaller pieces of data.

@pan-apple
Copy link
Contributor

I would also like to understand the scenario where the user of the secure session manager is aware of SPAKE based connection vs Sigma based connection. Currently, SPAKE based secure session is only used by Rendezvous state machine. It's not exposed to the controller or the ZCL layer.

The multiple peer connections between two nodes are supported, but mainly for

  1. Unicast/Multicast keys
  2. Key rolling (where the old key/session is being torn down and new session is being established)

The first could be disambiguated by adding a multicast API to the secure session manager.
The second should be self contained inside the secure session layer.

@kghost
Copy link
Contributor Author

kghost commented Nov 9, 2020

@andy31415

I would rather imagine we would like something like a 'connectionID' exposed that could be something like a pair<nodeid, exchangeid>. This sounds like a minimal information required rather than an entire large structure pointer.

Marked as 'changes requested' since I believe the PeerConnectionState to be too large of an object to pass around and ideally API boundaries should be with smaller pieces of data.

I agree with this part, an ExchangeContext could be enough for application, node id can be retrieved via the context. But here I'm proposing the internal relation ship between ExchangeContext and SecureSession. The Transport::PeerConnectionState * is only exposed to the ExchangeManager, not application.

@pan-apple

I would also like to understand the scenario where the user of the secure session manager is aware of SPAKE based connection vs Sigma based connection. Currently, SPAKE based secure session is only used by Rendezvous state machine. It's not exposed to the controller or the ZCL layer.

Problem 1 and 2 may not be a real problem, but 3 is. Lets focus on the changes.,

How to implement ExchangeContext::GetPeerCertification ? The ACL information is stored inside the certification, and applications need it to grant access permission. Using only node id to look up peer state may not be a good idea, it is kind of tricky and unstable.

@pan-apple
Copy link
Contributor

@andy31415

I would rather imagine we would like something like a 'connectionID' exposed that could be something like a pair<nodeid, exchangeid>. This sounds like a minimal information required rather than an entire large structure pointer.

Marked as 'changes requested' since I believe the PeerConnectionState to be too large of an object to pass around and ideally API boundaries should be with smaller pieces of data.

I agree with this part, an ExchangeContext could be enough for application, node id can be retrieved via the context. But here I'm proposing the internal relation ship between ExchangeContext and SecureSession. The Transport::PeerConnectionState * is only exposed to the ExchangeManager, not application.

@pan-apple

I would also like to understand the scenario where the user of the secure session manager is aware of SPAKE based connection vs Sigma based connection. Currently, SPAKE based secure session is only used by Rendezvous state machine. It's not exposed to the controller or the ZCL layer.

Problem 1 and 2 may not be a real problem, but 3 is. Lets focus on the changes.,

How to implement ExchangeContext::GetPeerCertification ? The ACL information is stored inside the certification, and applications need it to grant access permission. Using only node id to look up peer state may not be a good idea, it is kind of tricky and unstable.

What value (besides peer node ID) would be needed to look up the peer certificates? Is it peer's encryption key ID.

@kghost
Copy link
Contributor Author

kghost commented Nov 10, 2020

What value (besides peer node ID) would be needed to look up the peer certificates? Is it peer's encryption key ID.

Yes, using Tuple<NodeId, KeyId> may work. I have encountered another problem when writing message counter synchronization protocol, the application (protocol) needs to access message counter in the PeerConnectionState. If we directly associate ExchangeContext and PeerConnectionState we can reduce these lookups. And actually there is a one-to-many relationship between PeerConnectionState and ExchangeContext.

@rwalker-apple
Copy link
Contributor

@pan-apple, still looking at this?

@kghost
Copy link
Contributor Author

kghost commented Nov 12, 2020

I'm looking for suggestions to move forward. Both Tuple<NodeId, KeyId> and direct pointer can work. If we can decide to move this direction, we can compare them deeply.

@pan-apple
Copy link
Contributor

I'm looking for suggestions to move forward. Both Tuple<NodeId, KeyId> and direct pointer can work. If we can decide to move this direction, we can compare them deeply.

I don't have a strong opinion on either approach. Exposing the PeerConnectionState seems a bit excessive. How about we start with Tuple<NodeId, KeyId> and see if any other information is needed and add in future?

Comment on lines 73 to 81

VerifyOrExit(state->GetPeerNodeId() != kUndefinedNodeId, ChipLogProgress(AppServer, "Unknown source for received message"));

state->GetPeerAddress().ToString(src_addr, sizeof(src_addr));

ChipLogProgress(AppServer, "Packet received from %s: %zu bytes", src_addr, static_cast<size_t>(data_len));

HandleDataModelMessage(header, std::move(buffer), mgr);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe switch this code to reporting the node id instead of the source address (which it can get from the SecureSessionHandle , presumably, at least for a CASE session)? Either way, keeping the "we got N bytes logging" would be useful.

Copy link
Contributor Author

@kghost kghost Dec 11, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The member of the SecureSessionHandle is not decided yet. I don't want to expose the internal detail of SecureSessionHandle class.

I'm still considering how do we get information from session handle interface. Maybe:

auto session = mSecureSessionManager->GetSession(handle /* an secure session handle */);
bool isPaseSession = session->IsPaseSession(); // or session->IsCaseSession() for case check.
auto nodeid = session->GetPeerNodeId();
auto localKey = session->GetLocalKeyId();
auto peerKey = session->GetPeerKeyId();

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's fair, but we really will need a way to get the peer node ID for CASE sessions. And we really should keep the length logging here.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good, I'll make it so.

void Device::OnNewConnection(SecureSessionHandle session, SecureSessionMgr * mgr)
{
mState = ConnectionState::SecureConnected;
mSecureSession = session;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we expect SecureSessionHandle to always be copyable? Or are we likely to switch to move semantics on it at some point? Put another way, should there be an std::move() here?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, I'll expect that the handle is POD, and always be copyable. In the future, only messaging layer will use the handle, the handle class won't be exposed to applications or controller API.


void Device::OnConnectionExpired(SecureSessionHandle session, SecureSessionMgr * mgr)
{
mState = ConnectionState::NotConnected;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should mSecureSession be reset in some way?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

* @brief
* Called when a new pairing is being established
*
* @param state connection state
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There is no such param

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

*
* The receiver should release all resources associated with the connection.
*
* @param state connection state
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No such param...

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

@@ -191,6 +213,8 @@ class DLL_EXPORT Device

NodeId GetDeviceId() const { return mDeviceId; }

SecureSessionHandle GetSecureSession() const { return mSecureSession; }
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What will this return if OnNewConnection has not happened yet? Will it even be initialized memory?

Copy link
Contributor Author

@kghost kghost Dec 11, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'll remove GetSecureSession. The session should not be exposed.

@@ -395,6 +429,21 @@ void DeviceController::ReleaseAllDevices()
}
}

uint16_t DeviceController::FindDeviceIndex(SecureSessionHandle session)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I know this is what the code used to look like with node id, but is there a reason this is not FindDevice returning Device *, with nullptr returned if not found? Then we could use a ranged for loop instead of the indexing we are doing now, and callers wouldn't need to index back into the array; they would just use the pointer they got, if not null.

Copy link
Contributor Author

@kghost kghost Dec 11, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

While audit the usage of FindDeviceIndex, I don't think there is any reason preventing it from returning a Device *.

We can refactor it in another PR. but here I want to keep it consistent with FindDeviceIndex(NodeId id).

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yep, followup is fine. Just file an issue, please.

src/controller/CHIPDevice.h Outdated Show resolved Hide resolved
{
public:
SecureSessionHandle() : mPeerNodeId(kAnyNodeId), mPeerKeyId(0) {}
SecureSessionHandle(NodeId peerNodeId, uint16_t peerKeyId) : mPeerNodeId(peerNodeId), mPeerKeyId(peerKeyId) {}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should this constructor be protected, so only SecureSessionMgr can use it?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In the future, it is. But for now, InteractionModel need to create it using this one.

@kghost kghost force-pushed the tp-ss branch 3 times, most recently from 7ced116 to ac4f84e Compare December 14, 2020 12:56
@kghost
Copy link
Contributor Author

kghost commented Dec 16, 2020

@pan-apple @andy31415 this one is ready for merge, please do a final check.

In the future, I may need to change the member if SecureSesoinHandle class, since the internal of this class is never exposed, it should be a very easy task.

@kghost
Copy link
Contributor Author

kghost commented Jan 11, 2021

This one is ready for merge

@mspang
Copy link
Contributor

mspang commented Jan 11, 2021

This one is ready for merge

@kghost conflict

@rwalker-apple
Copy link
Contributor

@github-actions
Copy link

Size increase report for "esp32-example-build" from 4bb0e53

File Section File VM
chip-all-clusters-app.elf .flash.text 192 192
Full report output
BLOAT REPORT

Files found only in the build output:
    report.csv

Comparing ./master_artifact/chip-all-clusters-app.elf and ./pull_artifact/chip-all-clusters-app.elf:

sections,vmsize,filesize
.debug_info,0,1668
.debug_line,0,801
.debug_loc,0,508
.debug_str,0,333
.debug_ranges,0,312
.flash.text,192,192
.xt.prop._ZN4chip24SecureSessionMgrDelegate19OnConnectionExpiredENS_19SecureSessionHandleEPNS_16SecureSessionMgrE,0,76
.xt.prop._ZN4chip9Transport15PeerConnectionsILj16ELNS_4Time6SourceE0EE23FindPeerConnectionStateENS_8OptionalIyEEtPNS0_19PeerConnectionStateE,0,52
.debug_abbrev,0,24
.xt.prop._ZTVN4chip11DeviceLayer37DeviceNetworkProvisioningDelegateImplE,0,2
.strtab,0,-4
.symtab,0,-32
.xt.lit._ZNK4chip8OptionalINS_9Transport11PeerAddressEE5ValueEv,0,-40
.xt.prop._ZN4chip24SecureSessionMgrDelegate19OnConnectionExpiredEPKNS_9Transport19PeerConnectionStateEPNS_16SecureSessionMgrE,0,-76
.xt.prop._ZN4chip9Transport15PeerConnectionsILj16ELNS_4Time6SourceE0EE23FindPeerConnectionStateEyPNS0_19PeerConnectionStateE,0,-176
.shstrtab,0,-260


@github-actions
Copy link

Size increase report for "nrfconnect-example-build" from 4bb0e53

File Section File VM
chip-lighting.elf text 260 260
chip-lighting.elf shell_root_cmds_sections 12 12
chip-lock.elf text 260 260
chip-lock.elf shell_root_cmds_sections -4 -4
Full report output
BLOAT REPORT

Files found only in the build output:
    report.csv

Comparing ./master_artifact/chip-lighting.elf and ./pull_artifact/chip-lighting.elf:

sections,vmsize,filesize
.debug_info,0,1435
.debug_line,0,588
.debug_str,0,333
.debug_ranges,0,272
text,260,260
.debug_frame,0,92
.debug_abbrev,0,57
shell_root_cmds_sections,12,12
.strtab,0,-4
.debug_loc,0,-153

Comparing ./master_artifact/chip-lock.elf and ./pull_artifact/chip-lock.elf:

sections,vmsize,filesize
.debug_info,0,1435
.debug_line,0,588
.debug_str,0,333
.debug_ranges,0,272
text,260,260
.debug_frame,0,92
.debug_abbrev,0,57
.strtab,0,-4
shell_root_cmds_sections,-4,-4
.debug_loc,0,-145

Comparing ./master_artifact/chip-shell.elf and ./pull_artifact/chip-shell.elf:

sections,vmsize,filesize


@rwalker-apple rwalker-apple merged commit c07d818 into project-chip:master Jan 12, 2021
@yufengwangca
Copy link
Contributor

This change seems break the end-to-end messaging testing which is temporarily disabled in CI.

With this change, the app does not specify the target NodeID, but relay on the following callback to update the SecureSessionHandle.

void OnNewConnection(chip::SecureSessionHandle session, chip::SecureSessionMgr * mgr) override { 
    mSecureSession = session;
}

We can only specify one delegate of SecureSessionMgr at a time, but there are multiple connections managed by the SecureSessionMgr, how we make sure the SecureSessionMgr can pass the generated SecureSessionHandle to the corresponding target of its connection?

For example, there are multiple objects are set as the delegate of SecureSessionMgr, including DeviceController, ExchangeManager and application itself. The later SetDelegate() will overwrite the previous one.

Echo Application:
gSessionManager.SetDelegate(&gTestSecureSessionMgrDelegate); // Set the echo app as the delegate of SecureSessionMgr.
err = gExchangeManager.Init(&gSessionManager); // Set the ExchangeManager as the delegate of SecureSessionMgr.

The echo application won't received the generated SecureSessionHandle and it will sendMessage to garbage target.

@yufengwangca
Copy link
Contributor

Register #4451 to track this issue.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

8 participants