[IPython-dev] pyzmq authentication

Jason Grout jason-sage at creativetrax.com
Wed Jun 1 19:04:55 EDT 2011


On 6/1/11 1:33 PM, MinRK wrote:
> On Wed, Jun 1, 2011 at 11:20, Fernando Perez<fperez.net at gmail.com>  wrote:
>> On Wed, Jun 1, 2011 at 9:41 AM, MinRK<benjaminrk at gmail.com>  wrote:
>>> What we have currently is extremely primitive, and only meant to
>>> protect against accidental execution rather than
>>> malicious intrusion. The key is sent and checked with every message.
>>
>> If I understand correctly the link Jason sent, and from a quick
>> reading of the multiprocessing code, we should be able to use the same
>> machinery to avoid sending/receving the keys.  The main functions that
>> do the work in MP are in the 'connection' submodule, and they are
>> really two standalone functions:
>>
>> def deliver_challenge(connection, authkey):
>>     import hmac
>>     assert isinstance(authkey, bytes)
>>     message = os.urandom(MESSAGE_LENGTH)
>>     connection.send_bytes(CHALLENGE + message)
>>     digest = hmac.new(authkey, message).digest()
>>     response = connection.recv_bytes(256)        # reject large message
>>     if response == digest:
>>         connection.send_bytes(WELCOME)
>>     else:
>>         connection.send_bytes(FAILURE)
>>         raise AuthenticationError('digest received was wrong')
>>
>> def answer_challenge(connection, authkey):
>>     import hmac
>>     assert isinstance(authkey, bytes)
>>     message = connection.recv_bytes(256)         # reject large message
>>     assert message[:len(CHALLENGE)] == CHALLENGE, 'message = %r' % message
>>     message = message[len(CHALLENGE):]
>>     digest = hmac.new(authkey, message).digest()
>>     connection.send_bytes(digest)
>>     response = connection.recv_bytes(256)        # reject large message
>>     if response != WELCOME:
>>         raise AuthenticationError('digest sent was rejected')
>>
>>
>> They work with objects that have a basic socket interface, but
>> adapting this to zmq sockets should be possible.  Am I missing
>> something?
>
> We have 3 main channels:
> Shell (XREQ-XREP)
> Stdin (XREQ-XREQ) (I think?)
> IOPub (PUB-SUB)
>
> Of these, *only* the XREP can authenticate connections.  The reason is
> that none of the other sockets actually know where messages came from,
> so there's no mechanism for seeing if the sender previously had a
> successful handshake because you actually have no idea who the sender
> was.
>
> Even the client's XREQ socket cannot authenticate replies, it must
> trust that the replies are coming from the kernel.
>
> That said, the only easy one is the one that is the most important -
> the Kernel checking requests prior to execution. However, if you have
> someone sniffing on the line, it's trivial to spoof an authenticated
> socket - just copy the IDENT, which is sent in the clear over the
> wire, and the kernel will have exactly no idea that you aren't the
> original authenticated client.


If we don't have the concept of connection, then maybe we can 
authenticate each request by "signing" the requests.  For example, we 
could send a hash of the concatenation of the shared secret and the 
content of the message.  So a message would look like:

(signature, json_message)

The receiving code would then do the same process: concatenate the bytes 
in json_message, right off the wire, with the shared secret, do the same 
hash, and check the signature.  This would provide some assurance that 
whoever sent the message knew the shared secret.

Another thing we could do for a sequence of messages is to always 
increment a sequence number appended to the shared secret before sending 
the message, and send the hash of the shared secret + sequence number. 
This provides a series of keys, but each is a one-time key (the receiver 
tracks the sequence number, so it can ignore people that try to reuse 
these hashes).

Jason




More information about the IPython-dev mailing list