dominictarr / scuttlebutt

peer-to-peer replicatable data structure
Other
1.32k stars 66 forks source link

Security #6

Open dominictarr opened 12 years ago

dominictarr commented 12 years ago

I'm currently planning on adding a 5th field, signature, in order to implement a security model.

Ideas: from best to worst.

1) Each node will have a private key and use it to sign each update it creates. Public keys will be replicated between the nodes (& each source will sign it's public key)

This will make it easy for every node to validate an update from any other node. (note that only node's that will be creating updates need to replicate their public key)

2) An alternative could be to sign it with a secret shared between the client and the server, but then, all validation must be performed by the server. However, it will still be enough to prevent nodes from impersonating each other.

3) Another approach would be to associate each connection with the user, and apply the permissions as changes came in... this is the most traditional approach, but completely ruins the ability to have a random network topology, because you can only validate an update as it's coming in the door... ... but once it's inside there is no way to know "who's crashed the party".

4) for 'behind the firewall' use, have no security. -- not suitable for end-user applications, of course.

I'm expecting the sort of validation that will be needed will mostly be: a given source is allowed to update a given key, or keys or apply certain updates to certain keys..

the protocol may need to be extended to allow for reverts. This should probably be a part of the model.

Most validation errors will probably be due to bugs. run the validation on the client and the server, maybe just reinitializing the whole client model will be the simplest way to revert a validation error.

dominictarr commented 12 years ago

@Raynos what are your thoughts?

Raynos commented 12 years ago

@dominictarr security is as follows.

Everyone sends their identity with each packet. Everyone encrypts the entire packet using their private key.

There is some mechanism to turn identity -> public key. You decrypt the entire packet with the public key.

If the decrypted format validates then the packet came from the identity.

You now know who send you this packet and have access to public meta data about this individual. This should be all that's needed.

The hard part is generating private keys securely in browsers.

Raynos commented 12 years ago

@dominictarr how do we gaurd against man in the middle attacks A sends encrypted data to B. B sends A's packets to C pretending to be A

Raynos commented 12 years ago

As for getting private keys. drag & drop + html5 file api means drop your private SSH key into the app

dominictarr commented 12 years ago

I don't mean to encrypt the whole message, just sign it (i.e. add a digest as another field) doesn't make that much difference... although you'd need to make the source visible so you knew what key to decrypt it with.

dominictarr commented 12 years ago

the thing that differs from the usual way is that i'm not suggesting to encrypt the connection, but just the message. this means it doesn't matter what peers the message came from, you can be sure about who it originated from. a man-in-the-middle cannot send a message impersonating another, because he doesn't have the right private key to encrypt it - the fake message could easily be spotted.

It would be possible, however, to omit messages. you could get around this my periodically syncronizing the whole model via something more robust though, like a merkle tree. or just sync the whole model with someone more trustworthy.

hmmm, or maybe could use merkle trees instead...

Raynos commented 12 years ago

@dominictarr I like the idea of being able to identify who send an update package.

Could you explain in more detail how this would work. I'm not familiar

There is some public mapping between source and public keys somewhere?

dominictarr commented 12 years ago

Easy, you'd just replicate the public keys in over another scuttlebutt. A would replicate some key data about it self: it's id, and it's public key, and then sign it's public key & id combination this would prove that it has the private key to go with that id. You'd just have to have a validation rule that said other users couldn't overwrite another public key, id pair.

Of course, if you use a uuid, there is no chance another node will guess your id before you've declared it. now, it would be possible for a node to pretend to be many nodes -- multiple personality disorder --

This is a much smaller problem than impersonation of other nodes, though.

Raynos commented 12 years ago

@dominictarr how does signing work?

And what about

User B magically thinks its from user A

user B verifies digest is "magic_string_thats_correct"

dominictarr commented 12 years ago

with public private keys, you have a locking, and an unlocking key. the locking key encrypts a message so that only the unlocking key can open it, naturally.

now, either key can be made public (just not both, of course!)

I'm suggesting publishing the unlocking key. this way you create a hash of the message that anyone with your private key can prove is from you.

Raynos commented 12 years ago

person B now knows that person A wants this update to be applied to the scuttlebutt.

dominictarr commented 12 years ago

Exactly.

My thoughts on the use of this in practice:

Since the users & their permissions are probably gonna be replicated via scuttlebutt also, (if you are gonna make a peer to peer application, it all needs to be peer to peer)

so, the problem is, how does a new node know for sure who is who? I mean, a compromised node could sign someone else's id, and claim to be them.

How would you make the real slim shady stand up? I imagine you'll have a root user, you bestows permissions on other nodes. Say, the root node can issue commands, grant root permissions to other nodes, etc. Other nodes would only be able to update their own keys, (and not other node's keys)

Idea: so, you distribute the root public key to all the nodes, so they know who the root is before they are born. then, when you create a new node, it has to be 'baptized' by the root node. The other nodes will just ignore them, otherwise. Basically, the root would sign the node's (id, pub-key) pair.

hmm... that wouldn't work if you had multiple root nodes operating at once. Because a compromised node could pretend to be another node, but there would be a gap where one root could sign the first node, and another root could sign the fake node. unlikely, but there would be a gap where it could happen.

The attack would be to borrow someone elses ID and associate it with a new key pair, creating confusion about who the ID actually represented.

IDEA: instead of a random uuid, use a hash of the public key. this would be impossible to associate with another key pair - because it would be deterministically related to the key pair. So a node could not create a valid message that it did not have a public key for! It would be possible for new nodes to join the network, but the are guaranteed to be unique.

We will need to port more of the crypto lib to browserify, but there is plenty more crypto stuff to copy/paste I just didn't add more than I needed when I started crypto-browserify.

dominictarr commented 12 years ago

I've merged the security branch into master. basically, you pass a security object into the constructor.

the security object should be a singleton, except during testing.

has just three methods:

{ sign: function (update) {
    return _signature_
  },
  verify: function (update, cb) {
    // _isVerified_ should be a boolean.
    // error should only truthy if something "physical" went wrong
    // 404 like errors should become _isVerified_ = false.
    callback(err, _isVerified_) 
  },
  createId: function () {
     return _id_ //example: hash of the public key.
  }
}  

to do: add documention to readme.

dominictarr commented 12 years ago

hmm, one attack that is still possible is a compromised node could abstain from sending some messages. This could ruin eventually consistency. With nodes thinking they know something, but never get the correct information.

Here is an idea: when making the initial negotiation, each node could send how many significant messages it currently has.

{ source_id: [timestamp, count] }

Then if two nodes counts that don't make sense (like same timestamp, but different counts) Then they could just replicate all their messages for that count, retaining eventual consistency!

Raynos commented 12 years ago

@dominictarr what it there are N nodes, and N-1 bad actors. Surely the only single good actor in the network is doomed.

dominictarr commented 12 years ago

worse! he has no friends!

This would still be eventually consistent provided that good nodes are able to communicate directly occasionally. Also, some auth could be applied to the connection, like ssh, so that you can check that you are communicating with an approved node.

From my understanding, usually if a network is robust with up to N/2 - 1 bad actors, it's considered secure, after all, most people aren't criminals or frauds. You can't make something perfectly secure, you have to just make it sufficiently secure that it's not worth the effort to break in.

In all this, probably the biggest risk is the possibility we haven't discussed - that a private key is stolen. The security of the private key relies on the security of the operating system. This is out of the scope of the scuttlebutt protocol.

However, it is serious. If a private key is stolen, then it's not just DoS. The node could tell lies. How do you detect this? It's like, how what if someone you do business with has been blackmailed, or is doing some creative accounting? I don't think there is any easy way to verify this, you'd have to check whether there actions "make sense"

This is very interesting, but is an application level problem, or an application framework level problem.

The most basic approach would just be to have some way to banish certain nodes from the system.

kumavis commented 10 years ago

Idea to protect against when a private key is stolen

an update author could have (1) a signature for sending updates, and (2) a signature for deauthorizing itself (adding itself to the author blacklist) to be stored in a more secure location.

Though, this likely adds more problems than it removes:

dominictarr commented 10 years ago

Yes - I think an interesting way to do this would be to encrypt the revocation to some friends/family that you trust. You could encrypt it so that no single one of your friends can revoke your key, but you can retrive 4/5 of the encrypted revocations then you can reconstruct the revocation. If you pick friends who do not know each other, and know you well, then you could be confidant that they could verify your identity because they know you personally.

Another idea, is to make it so that each key must have a single chain of outputs - if you ever discover a fork, because the key has been copied to another computer, then you could assume that key is compromised...

In bitcoin, someone can steal your wallet... and spend your money. but with this model, they can only kill your identity, but not steal it (except temporarily). This is still pretty bad, but it's better because an attacker cannot profit out of hacking you. They can only spite you - like if you had an unbreakable bike lock, someone could put another bike lock over yours, double locking your bike and preventing you from accessing it. This would be very annoying, but this hardly ever happens because it's not profitable for the attacker.

kumavis commented 10 years ago

Ah the idea of multiple revocation keys that need to be combined is an interesting idea. Can you elaborate on the "single chain of outputs" and how to "discover a fork"?

dominictarr commented 10 years ago

like a bitcoin blockchain, or like a git commit log, except where merges means a conflict. Because, if one user controls one key on one device, then they can easily just make a singly linked list.

You still want to use multiple devices with the same identity but I think you could do that another way - by signing another key, to "delegate" to it. 1:1 key:device, but multiple keys per user.