document new API (d=get_*), add get_welcome()

This commit is contained in:
Brian Warner 2017-05-12 11:54:33 -07:00
parent 6604eae7a0
commit 2312f2ccce

View File

@ -30,10 +30,10 @@ from twisted.internet.defer import inlineCallbacks
def go(): def go():
w = wormhole.create(appid, relay_url, reactor) w = wormhole.create(appid, relay_url, reactor)
w.generate_code() w.generate_code()
code = yield w.when_code() code = yield w.get_code()
print "code:", code print "code:", code
w.send(b"outbound data") w.send_message(b"outbound data")
inbound = yield w.when_received() inbound = yield w.get_message()
yield w.close() yield w.close()
``` ```
@ -55,8 +55,8 @@ Delegated mode:
class MyDelegate: class MyDelegate:
def wormhole_got_code(self, code): def wormhole_got_code(self, code):
print("code: %s" % code) print("code: %s" % code)
def wormhole_received(self, data): # called for each message def wormhole_got_message(self, msg): # called for each message
print("got data, %d bytes" % len(data)) print("got data, %d bytes" % len(msg))
w = wormhole.create(appid, relay_url, reactor, delegate=MyDelegate()) w = wormhole.create(appid, relay_url, reactor, delegate=MyDelegate())
w.generate_code() w.generate_code()
@ -69,10 +69,10 @@ w = wormhole.create(appid, relay_url, reactor)
w.generate_code() w.generate_code()
def print_code(code): def print_code(code):
print("code: %s" % code) print("code: %s" % code)
w.when_code().addCallback(print_code) w.get_code().addCallback(print_code)
def received(data): def received(msg):
print("got data, %d bytes" % len(data)) print("got data, %d bytes" % len(msg))
w.when_received().addCallback(received) # gets exactly one message w.get_message().addCallback(received) # gets exactly one message
``` ```
## Application Identifier ## Application Identifier
@ -168,9 +168,9 @@ The Wormhole object has three APIs for generating or accepting a code:
far. A convenience wrapper is provided to attach this to the `rlcompleter` far. A convenience wrapper is provided to attach this to the `rlcompleter`
function of libreadline. function of libreadline.
No matter which mode is used, the `w.when_code()` Deferred (or No matter which mode is used, the `w.get_code()` Deferred (or
`delegate.wormhole_got_code(code)` callback) will fire when the code is `delegate.wormhole_got_code(code)` callback) will fire when the code is
known. `when_code` is clearly necessary for `generate_code`, since there's no known. `get_code` is clearly necessary for `generate_code`, since there's no
other way to learn what code was created, but it may be useful in other modes other way to learn what code was created, but it may be useful in other modes
for consistency. for consistency.
@ -224,7 +224,7 @@ The code-entry Helper object has the following API:
before display. before display.
* `h.choose_words(words)`: call this when the user is finished typing in the * `h.choose_words(words)`: call this when the user is finished typing in the
code. It does not return anything, but will cause the Wormhole's code. It does not return anything, but will cause the Wormhole's
`w.when_code()` (or corresponding delegate) to fire, and triggers the `w.get_code()` (or corresponding delegate) to fire, and triggers the
wormhole connection process. This accepts a string like "purple-sausages", wormhole connection process. This accepts a string like "purple-sausages",
without the nameplate. It must be called after `h.choose_nameplate()` or without the nameplate. It must be called after `h.choose_nameplate()` or
`MustChooseNameplateFirstError` will be raised. May only be called once, `MustChooseNameplateFirstError` will be raised. May only be called once,
@ -237,7 +237,7 @@ code-entry helper to do tab completion of wormhole codes:
from wormhole import create, input_with_completion from wormhole import create, input_with_completion
w = create(appid, relay_url, reactor) w = create(appid, relay_url, reactor)
input_with_completion("Wormhole code:", w.input_code(), reactor) input_with_completion("Wormhole code:", w.input_code(), reactor)
d = w.when_code() d = w.get_code()
``` ```
This helper runs python's (raw) `input()` function inside a thread, since This helper runs python's (raw) `input()` function inside a thread, since
@ -276,33 +276,13 @@ random values (e.g. by using the Diceware wordlist) unless that makes it
easier to transcribe: e.g. rolling 6 dice could result in a code like easier to transcribe: e.g. rolling 6 dice could result in a code like
"913-166532", and flipping 16 coins could result in "123-HTTHHHTTHTTHHTHH". "913-166532", and flipping 16 coins could result in "123-HTTHHHTTHTTHHTHH".
## Verifier
For extra protection against guessing attacks, Wormhole can provide a
"Verifier". This is a moderate-length series of bytes (a SHA256 hash) that is
derived from the supposedly-shared session key. If desired, both sides can
display this value, and the humans can manually compare them before allowing
the rest of the protocol to proceed. If they do not match, then the two
programs are not talking to each other (they may both be talking to a
man-in-the-middle attacker), and the protocol should be abandoned.
Deferred-mode applications can wait for `d=w.when_verified()`: the Deferred
it returns will fire with the verifier. You can turn this into hex or Base64
to print it, or render it as ASCII-art, etc.
Asking the wormhole object for the verifier does not affect the flow of the
protocol. To benefit from verification, applications must refrain from
sending any data (with `w.send(data)`) until after the verifiers are approved
by the user. In addition, applications must queue or otherwise ignore
incoming (received) messages until that point. However once the verifiers are
confirmed, previously-received messages can be considered valid and processed
as usual.
## Welcome Messages ## Welcome Messages
The first message sent by the rendezvous server is a "welcome" message (a The first message sent by the rendezvous server is a "welcome" message (a
dictionary). Clients should not wait for this message, but when it arrives, dictionary). This is sent as soon as the client connects to the server,
they should process the keys it contains. generally before the code is established. Clients should use
`d=get_welcome()` to get and process the `motd` key (and maybe
`current_cli_version`) inside the welcome message.
The welcome message serves three main purposes: The welcome message serves three main purposes:
@ -357,17 +337,45 @@ the library did not pay attention to the ERROR message, hence the server
should deliver errors in a WELCOME message if at all possible) should deliver errors in a WELCOME message if at all possible)
The `error` field is handled internally by the Wormhole object. The other The `error` field is handled internally by the Wormhole object. The other
fields are processed by an application-supplied "welcome handler" function, fields can be processed by application, by using `d=w.get_welcome()`. The
supplied as an argument to the `wormhole()` constructor. This function will Deferred will fire with the full welcome dictionary, so any other keys that a
be called with the full welcome dictionary, so any other keys that a future future server might send will be available to it.
server might send will be available to it. If the welcome handler raises
`WelcomeError`, the connection will be closed just as if an `error` key had
been received. The handler may be called multiple times (once per connection,
if the rendezvous connection is lost and then reestablished), so applications
should avoid presenting the user with redundant messages.
The default welcome handler will print `motd` to stderr, and will ignore Applications which need to display `motd` or an upgrade message, and wish to
`current_cli_version`. do so before using stdin/stdout for interactive code entry (`w.input_code()`)
should wait for `get_welcome()` before starting the entry process:
```python
@inlineCallbacks
def go():
w = wormhole.create(appid, relay_url, reactor)
welcome = yield w.get_welcome()
if "motd" in welcome: print welcome["motd"]
input_with_completion("Wormhole code:", w.input_code(), reactor)
...
```
## Verifier
For extra protection against guessing attacks, Wormhole can provide a
"Verifier". This is a moderate-length series of bytes (a SHA256 hash) that is
derived from the supposedly-shared session key. If desired, both sides can
display this value, and the humans can manually compare them before allowing
the rest of the protocol to proceed. If they do not match, then the two
programs are not talking to each other (they may both be talking to a
man-in-the-middle attacker), and the protocol should be abandoned.
Deferred-mode applications can wait for `d=w.get_verifier()`: the Deferred
it returns will fire with the verifier. You can turn this into hex or Base64
to print it, or render it as ASCII-art, etc.
Asking the wormhole object for the verifier does not affect the flow of the
protocol. To benefit from verification, applications must refrain from
sending any data (with `w.send_message(data)`) until after the verifiers are
approved by the user. In addition, applications must queue or otherwise
ignore incoming (received) messages until that point. However once the
verifiers are confirmed, previously-received messages can be considered valid
and processed as usual.
## Events ## Events
@ -377,36 +385,45 @@ functions on the delegate object. In Deferred mode, the application retrieves
Deferred objects from the wormhole, and event dispatch is performed by firing Deferred objects from the wormhole, and event dispatch is performed by firing
those Deferreds. those Deferreds.
* got_code (`yield w.when_code()` / `dg.wormhole_code(code)`): fired when the Most applications will only use `code`, `received`, and `close`.
wormhole code is established, either after `w.generate_code()` finishes the
generation process, or when the Input Helper returned by `w.input_code()` * code (`code = yield w.get_code()` / `dg.wormhole_got_code(code)`): fired
has been told `h.set_words()`, or immediately after `w.set_code(code)` is when the wormhole code is established, either after `w.generate_code()`
called. This is most useful after calling `w.generate_code()`, to show the finishes the generation process, or when the Input Helper returned by
generated code to the user so they can transcribe it to their peer. `w.input_code()` has been told `h.set_words()`, or immediately after
* key (`yield w.when_key()` / `dg.wormhole_key()`): fired when the `w.set_code(code)` is called. This is most useful after calling
key-exchange process has completed and a purported shared key is `w.generate_code()`, to show the generated code to the user so they can
established. At this point we do not know that anyone else actually shares transcribe it to their peer.
this key: the peer may have used the wrong code, or may have disappeared * key (`yield w.get_unverified_key()` /
altogether. To wait for proof that the key is shared, wait for `dg.wormhole_got_unverified_key(key)`): fired (with the raw master SPAKE2
`when_verified` instead. This event is really only useful for detecting key) when the key-exchange process has completed and a purported shared key
that the initiating peer has disconnected after leaving the initial PAKE is established. At this point we do not know that anyone else actually
shares this key: the peer may have used the wrong code, or may have
disappeared altogether. To wait for proof that the key is shared, wait for
`get_verifier` instead. This event is really only useful for detecting that
the initiating peer has disconnected after leaving the initial PAKE
message, to display a pacifying message to the user. message, to display a pacifying message to the user.
* verified (`verifier = yield w.when_verified()` / * verifier (`verifier = yield w.get_verifier()` /
`dg.wormhole_verified(verifier)`: fired when the key-exchange process has `dg.wormhole_got_verifier(verifier)`: fired when the key-exchange process
completed and a valid VERSION message has arrived. The "verifier" is a byte has completed and a valid VERSION message has arrived. The "verifier" is a
string with a hash of the shared session key; clients can compare them byte string with a hash of the shared session key; clients can compare them
(probably as hex) to ensure that they're really talking to each other, and (probably as hex) to ensure that they're really talking to each other, and
not to a man-in-the-middle. When `got_verifier` happens, this side knows not to a man-in-the-middle. When `get_verifier` happens, this side knows
that *someone* has used the correct wormhole code; if someone used the that *someone* has used the correct wormhole code; if someone used the
wrong code, the VERSION message cannot be decrypted, and the wormhole will wrong code, the VERSION message cannot be decrypted, and the wormhole will
be closed instead. be closed instead.
* version (`yield w.when_version()` / `dg.wormhole_version(versions)`: fired * versions (`versions = yield w.get_versions()` /
when the VERSION message arrives from the peer. This fires at the same time `dg.wormhole_got_versions(versions)`: fired when the VERSION message
as `verified`, but delivers the "app_versions" data (as passed into arrives from the peer. This fires just after `verified`, but delivers the
`wormhole.create(versions=)`) instead of the verifier string. "app_versions" data (as passed into `wormhole.create(versions=)`) instead
* received (`yield w.when_received()` / `dg.wormhole_received(data)`: fired of the verifier string. This is mostly a hack to make room for
forwards-compatible changes to the CLI file-transfer protocol, which sends
a request in the first message (rather than merely sending the abilities of
each side).
* received (`yield w.get_message()` / `dg.wormhole_got_message(msg)`: fired
each time a data message arrives from the peer, with the bytestring that each time a data message arrives from the peer, with the bytestring that
the peer passed into `w.send(data)`. the peer passed into `w.send_message(msg)`. This is the primary
data-transfer API.
* closed (`yield w.close()` / `dg.wormhole_closed(result)`: fired when * closed (`yield w.close()` / `dg.wormhole_closed(result)`: fired when
`w.close()` has finished shutting down the wormhole, which means all `w.close()` has finished shutting down the wormhole, which means all
nameplates and mailboxes have been deallocated, and the WebSocket nameplates and mailboxes have been deallocated, and the WebSocket
@ -419,27 +436,28 @@ those Deferreds.
## Sending Data ## Sending Data
The main purpose of a Wormhole is to send data. At any point after The main purpose of a Wormhole is to send data. At any point after
construction, callers can invoke `w.send(data)`. This will queue the message construction, callers can invoke `w.send_message(msg)`. This will queue the
if necessary, but (if all goes well) will eventually result in the peer message if necessary, but (if all goes well) will eventually result in the
getting a `received` event and the data being delivered to the application. peer getting a `received` event and the data being delivered to the
application.
Since Wormhole provides an ordered record pipe, each call to `w.send` will Since Wormhole provides an ordered record pipe, each call to `w.send_message`
result in exactly one `received` event on the far side. Records are not will result in exactly one `received` event on the far side. Records are not
split, merged, dropped, or reordered. split, merged, dropped, or reordered.
Each side can do an arbitrary number of `send()` calls. The Wormhole is not Each side can do an arbitrary number of `send_message()` calls. The Wormhole
meant as a long-term communication channel, but some protocols work better if is not meant as a long-term communication channel, but some protocols work
they can exchange an initial pair of messages (perhaps offering some set of better if they can exchange an initial pair of messages (perhaps offering
negotiable capabilities), and then follow up with a second pair (to reveal some set of negotiable capabilities), and then follow up with a second pair
the results of the negotiation). The Rendezvous Server does not currently (to reveal the results of the negotiation). The Rendezvous Server does not
enforce any particular limits on number of messages, size of messages, or currently enforce any particular limits on number of messages, size of
rate of transmission, but in general clients are expected to send fewer than messages, or rate of transmission, but in general clients are expected to
a dozen messages, of no more than perhaps 20kB in size (remember that all send fewer than a dozen messages, of no more than perhaps 20kB in size
these messages are temporarily stored in a SQLite database on the server). A (remember that all these messages are temporarily stored in a SQLite database
future version of the protocol may make these limits more explicit, and will on the server). A future version of the protocol may make these limits more
allow clients to ask for greater capacity when they connect (probably by explicit, and will allow clients to ask for greater capacity when they
passing additional "mailbox attribute" parameters with the connect (probably by passing additional "mailbox attribute" parameters with
`allocate`/`claim`/`open` messages). the `allocate`/`claim`/`open` messages).
For bulk data transfer, see "transit.md", or the "Dilation" section below. For bulk data transfer, see "transit.md", or the "Dilation" section below.
@ -531,7 +549,7 @@ What's good about dilated wormholes?:
Use non-dilated wormholes when your application only needs to exchange a Use non-dilated wormholes when your application only needs to exchange a
couple of messages, for example to set up public keys or provision access couple of messages, for example to set up public keys or provision access
tokens. Use a dilated wormhole to move large files. tokens. Use a dilated wormhole to move files.
Dilated wormholes can provide multiple "channels": these are multiplexed Dilated wormholes can provide multiple "channels": these are multiplexed
through the single (encrypted) TCP connection. Each channel is a separate through the single (encrypted) TCP connection. Each channel is a separate
@ -570,17 +588,19 @@ in python3):
## Full API list ## Full API list
action | Deferred-Mode | Delegated-Mode action | Deferred-Mode | Delegated-Mode
------------------ | -------------------- | -------------- ------------------ | ------------------ | --------------
w.generate_code() | | . | d=w.get_welcome() | dg.wormhole_got_welcome(welcome)
w.set_code(code) | | w.generate_code() | |
h=w.input_code() | | w.set_code(code) | |
. | d=w.when_code() | dg.wormhole_code(code) h=w.input_code() | |
. | d=w.when_verified() | dg.wormhole_verified(verifier) . | d=w.get_code() | dg.wormhole_got_code(code)
. | d=w.when_version() | dg.wormhole_version(version) . | d=w.get_unverified_key() | dg.wormhole_got_unverified_key(key)
w.send(data) | | . | d=w.get_verifier() | dg.wormhole_got_verifier(verifier)
. | d=w.when_received() | dg.wormhole_received(data) . | d=w.get_versions() | dg.wormhole_got_versions(versions)
key=w.derive_key(purpose, length) | | key=w.derive_key(purpose, length) | |
w.close() | | dg.wormhole_closed(result) w.send_message(msg) | |
. | d=w.close() | . | d=w.get_message() | dg.wormhole_got_message(msg)
w.close() | | dg.wormhole_closed(result)
. | d=w.close() |