~sil/desktopcouch/write-replication-status

« back to all changes in this revision

Viewing changes to desktopcouch/pair/couchdb_pairing/network_io.py

  • Committer: Tarmac
  • Author(s): Chad Miller
  • Date: 2009-09-11 18:02:23 UTC
  • mfrom: (56.2.7 pair-with-oauth)
  • Revision ID: bzrdev@chad.org-20090911180223-q6apqpx98iexypxo
Assume the couchdb is secure.  Big change, that.

Finally, remove pairings when asked to.

Transmit hostid and oauth information to paired devices.  

Show diffs side-by-side

added added

removed removed

Lines of Context:
35
35
hash = hashlib.sha512
36
36
 
37
37
 
 
38
def dict_to_bytes(d):
 
39
    """Convert a dictionary of string key/values into a string."""
 
40
    parts = list()
 
41
 
 
42
    for k, v in d.iteritems():
 
43
        assert isinstance(k, str), k
 
44
        l = len(k)
 
45
        parts.append(chr(l>>8))
 
46
        parts.append(chr(l&255))
 
47
        parts.append(k)
 
48
 
 
49
        assert isinstance(v, str), v
 
50
        l = len(v)
 
51
        parts.append(chr(l>>8))
 
52
        parts.append(chr(l&255))
 
53
        parts.append(v)
 
54
        
 
55
    blob = "".join(parts)
 
56
    l = len(blob)
 
57
    blob_size = list()
 
58
    blob_size.append(chr(l>>24))
 
59
    blob_size.append(chr(l>>16&255))
 
60
    blob_size.append(chr(l>>8&255))
 
61
    blob_size.append(chr(l&255))
 
62
 
 
63
    return "CMbydi0" + "".join(blob_size) + blob
 
64
 
 
65
 
 
66
def bytes_to_dict(b):
 
67
    """Convert a string from C{dict_to_bytes} back into a dictionary."""
 
68
    if b[:7] != "CMbydi0":
 
69
        raise ValueError("magic bytes missing.  Invalid string.  %r", b[:10])
 
70
    b = b[7:]
 
71
 
 
72
    blob_size = 0
 
73
    for c in b[:4]:
 
74
        blob_size = (blob_size << 8) + ord(c)
 
75
 
 
76
    blob = b[4:]
 
77
    if blob_size != len(blob):
 
78
        raise ValueError("bytes are corrupt; expected %d, got %d" % (blob_size,
 
79
            len(blob)))
 
80
 
 
81
    d = {}
 
82
    blob_cursor = 0
 
83
 
 
84
    while blob_cursor < blob_size:
 
85
        k_len = (ord(blob[blob_cursor+0])<<8) + ord(blob[blob_cursor+1])
 
86
        k = blob[blob_cursor+2:blob_cursor+2+k_len]
 
87
        blob_cursor += k_len + 2 
 
88
        v_len = (ord(blob[blob_cursor+0])<<8) + ord(blob[blob_cursor+1])
 
89
        v = blob[blob_cursor+2:blob_cursor+2+v_len]
 
90
        blob_cursor += v_len + 2
 
91
        d[k] = v
 
92
    return d
 
93
 
 
94
 
38
95
class ListenForInvitations():
39
96
    """Narrative "Alice".
40
97
 
41
98
    This is the first half of a TCP listening socket.  We spawn off
42
99
    processors when we accept invitation-connections."""
43
100
 
44
 
    def __init__(self, get_secret_from_user, on_close):
 
101
    def __init__(self, get_secret_from_user, on_close, hostid, oauth_data):
45
102
        """Initialize."""
46
103
        self.logging = logging.getLogger(self.__class__.__name__)
47
104
 
48
105
        self.factory = ProcessAnInvitationFactory(get_secret_from_user,
49
 
                on_close)
 
106
                on_close, hostid, oauth_data)
50
107
        self.listening_port = reactor.listenTCP(0, self.factory)
51
108
 
52
109
    def get_local_port(self):
84
141
        self.logging.debug("connection lost")
85
142
        basic.LineReceiver.connectionLost(self, reason)
86
143
 
87
 
    def lineReceived(self, message):
 
144
    def lineReceived(self, rich_message):
88
145
        """Handler for receipt of a message from the Bob end."""
89
 
        h = hash()
90
 
        digest_nybble_count = h.digest_size * 2
91
 
        self.expected_hash = message[0:digest_nybble_count]
92
 
        self.public_seed = message[digest_nybble_count:]
 
146
        d = bytes_to_dict(rich_message)
 
147
 
 
148
        self.expected_hash = d.pop("secret_message")
 
149
        self.public_seed = d.pop("public_seed")
 
150
        remote_hostid = d.pop("hostid")
 
151
        remote_oauth = d
 
152
 
93
153
        self.factory.get_secret_from_user(self.transport.getPeer().host,
94
154
                self.check_secret_from_user,
95
 
                self.send_secret_to_remote)
 
155
                self.send_secret_to_remote,
 
156
                remote_hostid, remote_oauth)
96
157
    
97
158
    def send_secret_to_remote(self, secret_message):
98
159
        """A callback for the invitation protocol to start a new phase 
101
162
        h = hash()
102
163
        h.update(self.public_seed)
103
164
        h.update(secret_message)
104
 
        self.sendLine(h.hexdigest())
 
165
        all_dict = dict()
 
166
        all_dict.update(self.factory.oauth_info)
 
167
        all_dict["hostid"] = self.factory.hostid
 
168
        all_dict["secret_message"] = h.hexdigest()
 
169
        self.sendLine(dict_to_bytes(all_dict))
105
170
        
106
171
    def check_secret_from_user(self, secret_message):
107
172
        """A callback for the invitation protocol to verify the secret
108
173
        that the user gives, against the hash we received over the
109
174
        network."""
 
175
 
110
176
        h = hash()
111
177
        h.update(secret_message)
112
178
        digest = h.hexdigest()
115
181
            h = hash()
116
182
            h.update(self.public_seed)
117
183
            h.update(secret_message)
118
 
            self.sendLine(h.hexdigest())
 
184
            all_dict = dict()
 
185
            all_dict.update(self.factory.oauth_info)
 
186
            all_dict["hostid"] = self.factory.hostid
 
187
            all_dict["secret_message"] = h.hexdigest()
 
188
            self.sendLine(dict_to_bytes(all_dict))
119
189
 
120
190
            self.logging.debug("User knew secret!")
121
191
 
132
202
 
133
203
    protocol = ProcessAnInvitationProtocol
134
204
 
135
 
    def __init__(self, get_secret_from_user, on_close):
 
205
    def __init__(self, get_secret_from_user, on_close, hostid, oauth_info):
136
206
        self.logging = logging.getLogger(self.__class__.__name__)
137
207
        self.get_secret_from_user = get_secret_from_user
138
208
        self.on_close = on_close
 
209
        self.hostid = hostid
 
210
        self.oauth_info = oauth_info
139
211
 
140
212
 
141
213
class SendInvitationProtocol(basic.LineReceiver):
153
225
 
154
226
        h = hash()
155
227
        h.update(self.factory.secret_message)
156
 
        self.sendLine(h.hexdigest() + self.factory.public_seed)
 
228
        d = dict(secret_message=h.hexdigest(),
 
229
                public_seed=self.factory.public_seed,
 
230
                hostid=self.factory.local_hostid)
 
231
        d.update(self.factory.local_oauth_info)
 
232
        self.sendLine(dict_to_bytes(d))
157
233
 
158
234
        h = hash()
159
235
        h.update(self.factory.public_seed)
161
237
        self.expected_hash_of_secret = h.hexdigest()
162
238
 
163
239
 
164
 
    def lineReceived(self, message):
 
240
    def lineReceived(self, rich_message):
165
241
        """Handler for receipt of a message from the Alice end."""
 
242
        d = bytes_to_dict(rich_message)
 
243
        message = d.pop("secret_message")
 
244
 
166
245
        if message == self.expected_hash_of_secret:
167
246
            remote_host = self.transport.getPeer().host
168
247
            try:
169
248
                remote_hostname = get_remote_hostname(remote_host)
170
249
            except dbus.exceptions.DBusException:
171
250
                remote_hostname = None
172
 
            self.factory.auth_complete_cb(remote_hostname, "(port)", "(info)")
 
251
            remote_hostid = d.pop("hostid")
 
252
            self.factory.auth_complete_cb(remote_hostname, remote_hostid, d)
173
253
            self.transport.loseConnection()
174
254
        else:
175
255
            self.logging.warn("Expected %r from invitation.",
188
268
    protocol = SendInvitationProtocol
189
269
 
190
270
    def __init__(self, auth_complete_cb, secret_message, public_seed,
191
 
            on_close):
 
271
            on_close, local_hostid, local_oauth_info):
192
272
        self.logging = logging.getLogger(self.__class__.__name__)
193
273
        self.auth_complete_cb = auth_complete_cb
194
274
        self.secret_message = secret_message
195
275
        self.public_seed = public_seed
196
276
        self.on_close = on_close
 
277
        self.local_hostid = local_hostid
 
278
        self.local_oauth_info = local_oauth_info
197
279
        self.logging.debug("initialized")
198
280
 
199
281
    def close(self):
214
296
 
215
297
        
216
298
def start_send_invitation(host, port, auth_complete_cb, secret_message,
217
 
        public_seed, on_close):
 
299
        public_seed, on_close, local_hostid, local_oauth):
218
300
    """Instantiate the factory to hold configuration data about sending an
219
301
    invitation and let the reactor add it to its event-handling loop by way of
220
302
    starting a TCP connection."""
221
303
    factory = SendInvitationFactory(auth_complete_cb, secret_message,
222
 
            public_seed, on_close)
 
304
            public_seed, on_close, local_hostid, local_oauth)
223
305
    reactor.connectTCP(host, port, factory)
224
306
 
225
307
    return factory