Implementing SSRS – Substrate Simple Remote Signer protocol#7365
Implementing SSRS – Substrate Simple Remote Signer protocol#7365
Conversation
Shouldn't it be https/wss? How do you secure the transport anyway? |
This is using regular jsonrpc_client_transports, which only support |
it is not a secure transport, especially for this task |
That is correct, and no one said it was. The expected environment for this until now, was to have a secured network, like run in a VPN and firewalled sub-network and/or expect the connection to be tunneled through external means (like SSH). I see though that the regular user might not realize they have to do that, so I could see that we might want hide that this behind a feature flag you'd have to explicitly activate until we have a secure-default-way of doing it instead. WDYT? |
Signer will be custom software implemented anyway. So I don't think there is much sense in using non-secure transport at all, when there are secure alternatives (for example, noise). If it is properly used, there will be no need in tunnelling and stuff. Noise has implementation in many languages if that is what required - even in pure JSON-RPC overhead need is also not clear to me, but I am not sure if this is critical. How much signing happens on validator node per second/minute? |
|
@NikVolf the goal was to get something out of the door quickly with the least developer overhead from our side to allow others to start experimenting with the workflow and use case – JSONRPC was all there and quick to get up and running, that's the only reason it was used. The way it stands, there are known security problems (like, the server still needs to have some knowledge of chain-info otherwise they can't securely sign VRFs, but that also means they have some connection to a chain or chain state, which requires internet to stay up to date) and performance issues (https and JSON being two things that could be replaced with more efficient things instead, but also that signing is mostly sync for in substrate still and the example and known others approaches also follow linear service patterns – like TEE can only do one signing at a time). |
|
I've a stupid question: I'd think half the tools for doing JSON RPC should do TLS, no? |
|
After conversation with @gnunicorn I've removed the @gnunicorn is extending the readme to explicitly state the above. (This shouldn't stop us from raising and documenting security-related issues with it, though — it's just by far not in shape to sustain any professional attention yet, and probably never will in its current form.) |
Maybe we should than put this into its own repo? And what is the path we continue here? I thought this issue is soo pressing that we can deprecate sentries? |
I must admit, I'm a bit confused here too. |
|
Converted this back to draft to more clearly signal:
Unfortunately, no other, more secure protocol has been proposed until now, which leaves us with a couple of path to go ahead, but not decided on any yet:
|
|
I'd assumed remote signers were vaguely HSM-like with special hardware connections, like a second ethernet card or USB connection. Yes, I think an internet remote signer wants a secure handshake like TLS but with fixed transport keys and IP addresses for both sides, so no CAs or DNS, but even then.. Interestingly, if you look at Noise IK and KK then you'll observe the difference is sending the initiators long term secret inside the first flow, encrypted only by an ephemeral key exchange. As the initiator knows the respondents long-term secret, one could've a hybrid handshake between IK and KK in which foreign initiators reveal their identity, which permits whitelisting on the first flow, but special distinguished initiators hide their identity against an adversary who compromises the respondents' IP address but not the respondent machine. In other words, your remote signer can open connections that look exactly like normal node connections, but avoids immediately exposing your remote singer's IP address against BGP attacks, etc. This is probably useless since a signer's messages look different anyways, but technically the traffic observing attacker looks different form the BGP attacker. |
|
For now, we consider our review done for this PR - please refer to issue 21 in the internal tracker. |
| ) | ||
| >, ServiceError> { | ||
| if config.keystore_remotes.len() > 1 { | ||
| return Err(ServiceError::Other( |
There was a problem hiding this comment.
In this case, why is it remotes and not remote?
| // FIXME: here would the concrete keystore been build, | ||
| // must return a concrete type (NOT `LocalKeystore`) that | ||
| // implements `CryptoStore` and `SyncCryptoStore` | ||
| Err("Remote Keystore not supported.") |
There was a problem hiding this comment.
Question: How would this be extended? do i have to fork substrate in this case? or is this crate node-template used to generate a new node implementation for the user?
| }) | ||
| } | ||
|
|
||
| /// Create a local keystore in memory. |
There was a problem hiding this comment.
This comment doesn't reflect what's happening inside the method
|
|
||
| /// A remote based keystore that is either memory-based or filesystem-based. | ||
| pub struct RemoteKeystore { | ||
| client: RwLock<Option<Client>>, |
There was a problem hiding this comment.
Why would we want to read/write lock on a remote client? IMO, locking should only happen on the server side.
| } | ||
| } | ||
|
|
||
| impl<Store: CryptoStore + 'static> Stream for KeystoreReceiver<Store> { |
There was a problem hiding this comment.
Wouldn't it be less verbose to have a method such as:
async fn run(mut self) {
loop {
request = self.receiver.next().await;
self.process_request(request)
}
}|
|
||
| fn sr25519_public_keys(&self, id: KeyTypeId) -> BoxFuture<Vec<sr25519::Public>> { | ||
| let receiver = self.send_request(RequestMethod::Sr25519PublicKeys(id)); | ||
| Box::new(receiver.map(|e| match e { |
There was a problem hiding this comment.
Nitpick: e here implies error but i think response here or result might better imply what the value is.
| impl crate::RemoteSignerApi for GenericRemoteSignerServer { | ||
|
|
||
| fn sr25519_public_keys(&self, id: KeyTypeId) -> BoxFuture<Vec<sr25519::Public>> { | ||
| let receiver = self.send_request(RequestMethod::Sr25519PublicKeys(id)); |
There was a problem hiding this comment.
Follow up to my above suggestion to use async fn, here send_request could be replaced with a direct call to the client's sr25519_public_keys which we can return as a boxed future without .await.
|
|
||
| enum KeystoreContainerInner { | ||
| Local(Arc<LocalKeystore>) | ||
| trait AsCryptoRef { |
There was a problem hiding this comment.
Nitpick: can this be renamed to AsCryptoStoreRef?
|
|
||
| struct DoubleWrap<T>(Arc<T>); | ||
|
|
||
| impl<T> AsCryptoRef for DoubleWrap<T> where T: CryptoStore + SyncCryptoStore + 'static { |
There was a problem hiding this comment.
Question: What happens if we get rid of DoubleWrap and implement AsCryptoRef on Arc<T> directly? the below methods could return self.clone() in that case and the code would be a bit simpler.
I have modified this snippet you showed me earlier to do this:
https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=5d6a9deb6c38c00cab9d0198b95d690c
| /// Construct and hold different layers of Keystore wrappers | ||
| pub struct KeystoreContainer(KeystoreContainerInner); | ||
| pub struct KeystoreContainer { | ||
| remotes: Vec<Box<dyn AsCryptoRef>>, |
There was a problem hiding this comment.
If we plan to support multiple remotes, it would be nice to document the use case.
|
superseeded by #7628 . |
Implementation of the jsonrpc remote keystore over the http/ws protocol as the substrate keystore, including a separate example server implementation.
To Do:
Note: test had ecdsa support, but was removed because of #7490