Bootstrap interface availability after third party introduction

216 views
Skip to first unread message

Tim Popham

unread,
Feb 7, 2015, 2:29:10 PM2/7/15
to capn...@googlegroups.com
I know that Level 3 doesn't exist theoretically, but I'm hoping that the die has been cast for a few fundamentals.  Will a third-party introduction confer permission to use a capability host's bootstrap methods?  If not, has any consideration been given for an additional bootstrap interface exposed on third-party introduced connections?

Kenton Varda

unread,
Feb 7, 2015, 3:45:18 PM2/7/15
to Tim Popham, capnproto
Hi Tim,

The bootstrap interface will be exposed on all connections made through the associated VatNetwork. There's no fundamental difference between a connection made as a result of a three-party interaction vs. one made directly; in fact, during a three-party introduction, if the introductees already have a preexisting connection open, they'll just use that.

With that said, on a VatNetwork that has more than two parties, since the bootstrap interface is effectively public to everyone on the network, it's important that the bootstrap interface itself not grant any meaningful authority. It should only be used as a way to exchange other forms of capabilities for Cap'n Proto capabilities. For example, it might have a restore() method that takes a SturdyRef (or some other sort of unguessable token) and returns the live ref.

-Kenton

On Sat, Feb 7, 2015 at 11:29 AM, Tim Popham <impo...@gmail.com> wrote:
I know that Level 3 doesn't exist theoretically, but I'm hoping that the die has been cast for a few fundamentals.  Will a third-party introduction confer permission to use a capability host's bootstrap methods?  If not, has any consideration been given for an additional bootstrap interface exposed on third-party introduced connections?

--
You received this message because you are subscribed to the Google Groups "Cap'n Proto" group.
To unsubscribe from this group and stop receiving emails from it, send an email to capnproto+...@googlegroups.com.
Visit this group at http://groups.google.com/group/capnproto.

Tim Popham

unread,
Feb 7, 2015, 5:12:57 PM2/7/15
to capn...@googlegroups.com, impo...@gmail.com
The bootstrap interface will be exposed on all connections made through the associated VatNetwork. There's no fundamental difference between a connection made as a result of a three-party interaction vs. one made directly; in fact, during a three-party introduction, if the introductees already have a preexisting connection open, they'll just use that.

10-4.  I was (wrongly) operating under the assumption that authorization should be rolled into connection negotiation before exchanging Capnproto messages (and maybe that's okay for levels 2 and prior).

With that said, on a VatNetwork that has more than two parties, since the bootstrap interface is effectively public to everyone on the network, it's important that the bootstrap interface itself not grant any meaningful authority. It should only be used as a way to exchange other forms of capabilities for Cap'n Proto capabilities. For example, it might have a restore() method that takes a SturdyRef (or some other sort of unguessable token) and returns the live ref.

The intended use for my misguided "additional bootstrap interface" is to authenticate a public key holder's private key ownership (on a transport that handles encryption for free).  Instead it sounds like I should be looking at a bootstrap method like

```
    decrypt @n (encrypted :Data) -> (decrypted :Data);
```

Then I can verify that the public key holder decrypts some nonce.

Kenton Varda

unread,
Feb 8, 2015, 2:24:04 AM2/8/15
to Tim Popham, capnproto
On Sat, Feb 7, 2015 at 2:12 PM, Tim Popham <impo...@gmail.com> wrote:
The bootstrap interface will be exposed on all connections made through the associated VatNetwork. There's no fundamental difference between a connection made as a result of a three-party interaction vs. one made directly; in fact, during a three-party introduction, if the introductees already have a preexisting connection open, they'll just use that.

10-4.  I was (wrongly) operating under the assumption that authorization should be rolled into connection negotiation before exchanging Capnproto messages (and maybe that's okay for levels 2 and prior). 

Some authentication makes sense at connection time. In a three-party introduction, you want to make sure the vat you are connecting to is actually the vat the third party told you to connect to. But, this is a rather easier authentication problem that usual: you only need to confirm that some live vat is the same vat that some other vat you're talking to, is talking to. This doesn't require any long-term notion of identity.

For example, each vat (process) could create a new random public/private keypair at startup and never even store the private key to disk. Then say you have three vats Alice, Bob, and Carol. Alice has a capability pointing towards Carol and sends that capability to Bob. Alice knows Carol's public key (since she's already connected to Carol) and so she tells Bob to connect to that same public key. So Bob only then needs to authenticate that Carol in fact holds the private key associated with that public key.

All this should happen at the VatNetwork level. If your application also needs a notion of authentication, it should implement that separately via RPC interfaces, as you suggest. Your application's authentication probably depends on identities that are longer-term than a single process.

FWIW, I'm currently working on implementing the VatNetwork I describe above in terms of ed25519 / curve25519.

With that said, on a VatNetwork that has more than two parties, since the bootstrap interface is effectively public to everyone on the network, it's important that the bootstrap interface itself not grant any meaningful authority. It should only be used as a way to exchange other forms of capabilities for Cap'n Proto capabilities. For example, it might have a restore() method that takes a SturdyRef (or some other sort of unguessable token) and returns the live ref.

The intended use for my misguided "additional bootstrap interface" is to authenticate a public key holder's private key ownership (on a transport that handles encryption for free).  Instead it sounds like I should be looking at a bootstrap method like

```
    decrypt @n (encrypted :Data) -> (decrypted :Data);
```

Then I can verify that the public key holder decrypts some nonce.

Yes, although of course you probably want something a bit more restricted than that -- you don't want everyone who connects to you to be able to decrypt arbitrary data with you key by just passing it to this interface. :)

-Kenton

Tony Arcieri

unread,
Feb 9, 2015, 11:25:30 AM2/9/15
to Kenton Varda, Tim Popham, capnproto
On Sat, Feb 7, 2015 at 11:23 PM, Kenton Varda <ken...@sandstorm.io> wrote:
FWIW, I'm currently working on implementing the VatNetwork I describe above in terms of ed25519 / curve25519.

Do you have a protocol description posted anywhere? 

--
Tony Arcieri

Kenton Varda

unread,
Feb 9, 2015, 12:48:21 PM2/9/15
to Tony Arcieri, Tim Popham, capnproto
No, because I haven't actually done any significant work on this yet. :)

But the basic idea is simple: Each process generates an ed25519/curve25519 key pair at startup. The private key is pinned into RAM and stays there. When the process exits or dies the key is gone.

In Cap'n Proto introductions, when Alice tells Bob to talk to Carol, Alice can tell Bob Carol's public key. So when Bob connects to Carol, he already has her key, certified by Alice.

So, the protocol is:
- Bob announces to Carol that he wishes to connect, providing his own public key.
- Both sides can generate a shared secret via curve25519.
- The shared secret encrypts the connection using an appropriate cipher.

Now, it's of course true that this approach is missing "perfect forward secrecy": if you compromise a process's private key then you can decrypt all of its past communications with all parties. However, the key is never stored anywhere else but the process's own RAM. If you can compromise that, you can probably just pull all the symmetric keys out of the process anyway.

The reason I want to make this compromise is because it makes it possible for Bob to start sending an encrypted stream without waiting for any handshake from Carol. Using UDP (or TCP fast open?) all round trips can theoretically be avoided.

For bootstrap connections (an initial connection made between two parties not as a result of a Cap'n Proto introduction), we'll need some other way for the client to know the server's public key ahead of time, and I don't yet have opinions there. Maybe the client just fetches it via HTTPS, thus bootstrapping off our existing infrastructure.

Thoughts? Who should I talk to to verify that I'm not missing anything?

-Kenton

Andrew Lutomirski

unread,
Feb 9, 2015, 5:57:59 PM2/9/15
to Kenton Varda, Tony Arcieri, Tim Popham, capnproto
On Mon, Feb 9, 2015 at 9:47 AM, Kenton Varda <ken...@sandstorm.io> wrote:
> On Mon, Feb 9, 2015 at 8:25 AM, Tony Arcieri <bas...@gmail.com> wrote:
>>
>> On Sat, Feb 7, 2015 at 11:23 PM, Kenton Varda <ken...@sandstorm.io> wrote:
>>>
>>> FWIW, I'm currently working on implementing the VatNetwork I describe
>>> above in terms of ed25519 / curve25519.
>>
>>
>> Do you have a protocol description posted anywhere?
>
>
> No, because I haven't actually done any significant work on this yet. :)
>
> But the basic idea is simple: Each process generates an ed25519/curve25519
> key pair at startup. The private key is pinned into RAM and stays there.
> When the process exits or dies the key is gone.

FWIW, this might not be optimal from a simplicity or performance
perspective. You're using the key pair for identification, not
non-repudiation, and you can get this using just DH as a primitive.
See, for example, triple-DH. The idea is that each party publishes a
Curve25519 public key and the corresponding private key is used for
identification by demonstrating the ability to do DH with the
corresponding private key.

The performance improvement is non-negligible, IIRC. This replaces a
signature operation with a variable-base scalar multiplication. It
also avoids needing a copy of the ed25519 code, which isn't *that*
similar to curve25519.

--Andy

Tony Arcieri

unread,
Feb 9, 2015, 6:03:41 PM2/9/15
to Andrew Lutomirski, Kenton Varda, Tim Popham, capnproto
On Mon, Feb 9, 2015 at 2:57 PM, Andrew Lutomirski <an...@luto.us> wrote:
FWIW, this might not be optimal from a simplicity or performance
perspective.  You're using the key pair for identification, not
non-repudiation, and you can get this using just DH as a primitive.
See, for example, triple-DH.  The idea is that each party publishes a
Curve25519 public key and the corresponding private key is used for
identification by demonstrating the ability to do DH with the
corresponding private key.

Yes, more specifically you want to use X25519 public keys for each node (Montgomery form x-coordinates)
 
The performance improvement is non-negligible, IIRC.  This replaces a
signature operation with a variable-base scalar multiplication.  It
also avoids needing a copy of the ed25519 code, which isn't *that*
similar to curve25519.

I'm guessing that Kenton didn't actually mean using EdDSA (merely that Curve25519/Ed25519 are isogenous/birationally equivalent), but yes, unless you have multiple recipients for the same message, there's no reason to use EdDSA over X25519. Deriving a shared secret between two parties and using a MAC (or authenticated encryption) should be both faster and more secure.

--
Tony Arcieri

Tim Popham

unread,
Feb 9, 2015, 9:01:08 PM2/9/15
to capn...@googlegroups.com, impo...@gmail.com
With that said, on a VatNetwork that has more than two parties, since the bootstrap interface is effectively public to everyone on the network, it's important that the bootstrap interface itself not grant any meaningful authority. It should only be used as a way to exchange other forms of capabilities for Cap'n Proto capabilities. For example, it might have a restore() method that takes a SturdyRef (or some other sort of unguessable token) and returns the live ref.

I've got another use case.  I've been looking at how to initiate a connection between 2 web browsers with the RTC W3C API, but without a 4th party.  I was looking to include a capability in `ThirdPartyCapId` and `RecipientId`, but I quit because I would have to do some hackery around an ad hoc `capTable` (for starters).  This requires a signalling channel through the Introducer.  I think that the Introducer's bootstrap interface should handle the job:

```
struct Maybe(Just) {
    union {
        nothing @0 :Void;
        just @1 :Just;
    }
}

interface Writable(Message) {
    write @0 (message :Message);
}

struct Rtc {
    struct Offer {
        # The Recipient public key encrypts payloads so that only a
        # `providerKey`'s private key holder can participate.
        union {
            sdpOffer @0 :Encrypted(Text);
            iceOffer @1 :Encrypted(Ice);
        }
    }

    struct Answer {
        # The Provider public key encrypts payloads so that only a
        # `recipientKey`'s private key holder can participate.
        union {
            sdpAnswer @0 :Encrypted(Text);
            iceAnswer @1 :Encrypted(Ice);
        }
    }

    struct Ice {...}
}

interface IntroducerBootstrap {
    # The third party fulfills an `accept` call with the `in` of a corresponding
    # `provide` call, and vice versa.  (The `Maybe` nothings if a connection is
    # established over a different transport.)

    provide @0 (recipientKey :PublicKey, in :Writable(Rtc.Offer)) ->
        (out :Maybe(Writable(Rtc.Answer)));
    # Trade 'write offers to provider/caller' for 'write answers to recipient'.

    accept @1 (providerKey :PublicKey, in :Writable(Rtc.Answer)) ->
        (out :Maybe(Writable(Rtc.Offer)));
    # Trade 'write answers to recipient/caller' for 'write offers to provider'.
}
```

Kenton Varda

unread,
Feb 12, 2015, 2:04:12 AM2/12/15
to Tim Popham, capnproto
Hmm, well, I haven't actually implemented any three-party RPC stuff yet. :)

Do you have RPC working in Javascript at this point?

I would think that in the use case you describe, you'd usually have the two browsers talking to some specific object on the server before they want to connect to each other (otherwise, how did they find out about each other in the first place?). So instead of using the bootstrap interface, you'd do the negotiation through that shared object.

For instance, say you have a video chat room app that uses RTC. Each user would first connect to some chat room at the server, which would be represented as a Cap'n Proto object. Then they'd exchange RTC initiation info through that object.

How do things work in your use case? How do the two users' browsers initially decide that they want to connect?

-Kenton

Tim Popham

unread,
Feb 12, 2015, 3:20:28 PM2/12/15
to capn...@googlegroups.com, impo...@gmail.com
Hmm, well, I haven't actually implemented any three-party RPC stuff yet. :)

Me neither.  I'm sketching use cases for building up connection infrastructure, and I've got a fix on the API that I'm chasing.
 
Do you have RPC working in Javascript at this point?

Not even level 0.  I'm aiming for 0, 1, and 3 initially.

I would think that in the use case you describe, you'd usually have the two browsers talking to some specific object on the server before they want to connect to each other (otherwise, how did they find out about each other in the first place?). So instead of using the bootstrap interface, you'd do the negotiation through that shared object.

For instance, say you have a video chat room app that uses RTC. Each user would first connect to some chat room at the server, which would be represented as a Cap'n Proto object. Then they'd exchange RTC initiation info through that object.

My interest in RTC is specifically in data channels as a transport for Capnproto RPC between browsers.  I'm looking at the bootstrap for negotiating third party introductions.  (With an initial peer connection established automagically by Capnproto RPC, my impression is that multiplexing peer-to-peer media exchange ought to be easy.)

Specifically, I'm looking at a vat network with RTC and WebSocket connections.  I could leverage a WebSocket server as an identity assigning authority on the network and signal RTC connections through that authority.  That's the vague "4th party" that I'm trying to avoid.

Kenton Varda

unread,
Feb 13, 2015, 9:26:12 PM2/13/15
to Tim Popham, capnproto
Hmm, I'm not sure if I understand what you mean by "fourth party". Let me state my best guess and you can tell me if I'm wrong.

Alice, Bob, and Carol (the first three parties) are all browsers. Alice has RTC connections to Bob and Carol, and wishes to introduce Bob to Carol.

Dave, the fourth party, is a server somewhere. You believe you need this server to help negotiate connections. Dave exports a bootstrap interface that can be used for this.

What I don't see here is why Dave is needed. Why can't Alice facilitate the introduction directly?

BTW, I would *love* to have a Cap'n Proto transport that automagically uses WebSocket for browser<->server, WebRTC for browser<->browser, and IP for server<->server. :)

-Kenton

--

Tim Popham

unread,
Feb 14, 2015, 2:32:15 AM2/14/15
to capn...@googlegroups.com, impo...@gmail.com
Hmm, I'm not sure if I understand what you mean by "fourth party". Let me state my best guess and you can tell me if I'm wrong.

Alice, Bob, and Carol (the first three parties) are all browsers. Alice has RTC connections to Bob and Carol, and wishes to introduce Bob to Carol.

Dave, the fourth party, is a server somewhere. You believe you need this server to help negotiate connections. Dave exports a bootstrap interface that can be used for this.

What I don't see here is why Dave is needed. Why can't Alice facilitate the introduction directly?

Sorry for the muddled mess.  Let me summarize.

The network that I want to parametrize includes Websocket servers, Websocket clients, and Rtc peers.  By my personal biases and my reading of the commentary on `ThirdPartyCapId` from `rpc.capnp`, I expected each network member to have an address for each transport that it supports, e.g.

```
struct ThirdPartyStructId {
    # This is no good
    tcp @0 :Maybe(TcpAddress);
    websocket @1 :Maybe(WebSocketAddress);
    rtc @2 :Maybe(RtcAddress);
    # ...
}
# and an analogous RecipientId
```

(See sipjs and peerjs for my personal bias--any existing RTC systems that I've seen use pub-sub or `RtcAddress`-like analogues.)

My first instinct was to introduce a WebSocket server, Dave.  Any vat that supports RTC connections would register with Dave, and thereby obtain an `RtcAddress`.  The signaling to setup a connection between two RTC peers would go through Dave.  In this scenario,
* Alice sends a `Provide` (including Carol's `RtcAddress`) to Bob,
* Alice sends a `ThirdPartyCapId` (including Bob's `RtcAddress`) to Carol, and then
* Bob and Carol dispatch signaling message to one another through Dave.
This sucks:  If I can signal through Alice somehow, then Dave can mine bitcoin or something.

So the goal is to dispatch messages through Alice instead of Dave....

My new first instinct was to embed a signaling capability in `ThirdPartyStructId` and `RecipientId`.  I think that this is feasible, but I expect bridging to other Capnproto implementations would be difficult.

My current solution uses Alice's bootstrap interface to signal between Bob and Carol.  I anticipate extending all of the network's bootstrap interfaces from `MainBase`:

```
struct Rtc {
    interface Offer {
        write @0 Message;

        struct Message {
            # The Recipient public key encrypts payloads so that only a
            # `sourceKey`'s private key holder can participate.
            union {
                sdp @0 :Encrypted(Text);
                ice @1 :Encrypted(Ice);
            }
        }
    }

    interface Answer {
        write @0 Message;

        struct Message {
            # The Source public key encrypts payloads so that only a
            # `recipientKey`'s private key holder can participate.
            union {
                sdp @0 :Encrypted(Text);
                ice @1 :Encrypted(Ice);
            }
        }
    }

    struct Ice {...}
}

struct ThirdPartyCapId {
    supportsRtc @0 :Bool;
    # ...
}
# and an analogous RecipientId

interface MainBase {
    provide @0 (recipientKey :PublicKey, in :Rtc.Offer) ->
        (out :Rtc.Answer) $noProvide;

    accept @1 (sourceKey :PublicKey, in :Rtc.Answer) ->
        (out :Rtc.Offer) $noProvide;
}
```

The signaling flow with this bootstrap interface:
* Alice sends a `Provide` (indicating that Carol supports RTC) to Bob,
* Alice sends a `ThirdPartyCapId` (indicating that Bob supports RTC) to Carol, and then
* Bob creates an `Rtc.Offer` and he calls the `provide` method on Alice's bootstrap `aliceBootstrap.provideRequest(carolKey, offer)`--Alice won't resolve the request until she receives an `Rtc.Answer` from Carol,
* Carol creates an `Rtc.Answer` and she calls the `accept` method on Alice's bootstrap `aliceBootstrap.acceptRequest(bobKey, answer)`--Alice won't resolve the request until she receives an `Rtc.Offer` from Bob, and then
* Alice sees an offer from Bob targeting Carol's key, and she sees an answer from Carol targeting Bob's key, and then
* Alice resolves both of the requests (annoyingly, additional Provide messages are flying around when only the vine is needed, hence the `noProvide` annotation),
* Carol's request resolves to an `Rtc.Offer` instance:  she writes `Rtc.Offer.Message`s to Bob,
* Bob's request resolves to an `Rtc.Answer` instance:  he responds to offers with `Rtc.Answer.Message`s to Carol,
* ...
I suspect that this is the right way to facilitate introductions without a 4th party.  And an introduction involving only 3 parties is strictly superior to an introduction involving 4 parties.  This seems like a legitimate use of the bootstrap interface on a level 3 network.

Sorry, again, about the muddled mess.

BTW, I would *love* to have a Cap'n Proto transport that automagically uses WebSocket for browser<->server, WebRTC for browser<->browser, and IP for server<->server. :)

Me too (I'm so sick of seeing peer-to-peer webapps build on socket.io, I could puke blood).  The lack of a WebSocket server API in the browser makes the browser-provides-to-WebSocket-server case a little goofy, but I think I got it figured.

Kenton Varda

unread,
Feb 23, 2015, 11:23:56 PM2/23/15
to Tim Popham, capnproto
Hi Tim,

OK, I get it now!

It seems that what we'd really like here is if one RTC offer and answer could be reused for multiple connections. Then, Bob and Carol would both give an offer and an answer to Alice when they first connect, and Alice would be able to pass an "answer" in ThirdPartyCapId and an "offer" in RecipientId, allowing Bob and Carol to form a connection without any additional round trip back to Alice.

Assuming WebRTC isn't awesome enough to support that, I would argue that what you are doing here should be built into your transport layer spec. That is, over an RTC connection, you'd send messages like:

    message RtcMessage {
      union {
        rpcMessage @0 :Rpc.Message; # standard RPC message

        sendOffer @1 :SendOffer;
        sendAnswer @2 :SendAnswer;
        # Messages for RTC handshake, sent in response to an introduction.
      }
    }

These special messages would be handled entirely in the transport (aka VatNetwork) implementation so that the application layer never sees them. The application can still define its own bootstrap interface as desired.

> The lack of a WebSocket server API in the browser makes the
> browser-provides-to-WebSocket-server case a little goofy, but I think I got it figured.

I think the way around this is that whenever a browser is introduced to a server, the browser initiates the connection, regardless of who is the provider and who is the receiver. When a server is told to receive a capability from a browser, the server waits, expecting an incoming connection.

I think that in this transport:
- browser<->browser = WebRTC, always initiated via a 3-party introduction.
- browser<->server = WebSocket, always browser-initiated, either as a result of an introduction or for bootstrap.
- server<->server = Whatever standard Cap'n Proto IP-based transport protocol we come up with.

I'm really excited that you're working on this!

-Kenton

--

Tim Popham

unread,
Feb 24, 2015, 2:19:02 AM2/24/15
to capn...@googlegroups.com, impo...@gmail.com
It seems that what we'd really like here is if one RTC offer and answer could be reused for multiple connections. Then, Bob and Carol would both give an offer and an answer to Alice when they first connect, and Alice would be able to pass an "answer" in ThirdPartyCapId and an "offer" in RecipientId, allowing Bob and Carol to form a connection without any additional round trip back to Alice.

No good :(

The initial offer contains a session id, and the initial answer is predicated on the initial offer.

Assuming WebRTC isn't awesome enough to support that, I would argue that what you are doing here should be built into your transport layer spec. That is, over an RTC connection, you'd send messages like:

    message RtcMessage {
      union {
        rpcMessage @0 :Rpc.Message; # standard RPC message

        sendOffer @1 :SendOffer;
        sendAnswer @2 :SendAnswer;
        # Messages for RTC handshake, sent in response to an introduction.
      }
    }

These special messages would be handled entirely in the transport (aka VatNetwork) implementation so that the application layer never sees them. The application can still define its own bootstrap interface as desired.

Oops, you're right.  I think my original intent (pre-`$noProvide`) was to allow the signalling channel to collapse to a direct connection and then use that channel to signal the opening of media streams, unreliable data channels, etc. on demand, but RPC is better suited to the task.  I'm left without a use case.

> The lack of a WebSocket server API in the browser makes the
> browser-provides-to-WebSocket-server case a little goofy, but I think I got it figured.

I think the way around this is that whenever a browser is introduced to a server, the browser initiates the connection, regardless of who is the provider and who is the receiver. When a server is told to receive a capability from a browser, the server waits, expecting an incoming connection.

That's what I'm figuring.  It just looks a little funny against `rpc.capnp`'s network interface sketch.  (I'm not complaining that the sketch should be changed:  it just looks a little funny.)

I think that in this transport:
- browser<->browser = WebRTC, always initiated via a 3-party introduction.
- browser<->server = WebSocket, always browser-initiated, either as a result of an introduction or for bootstrap.
- server<->server = Whatever standard Cap'n Proto IP-based transport protocol we come up with.

On the server<->server case, I'll probably be back hat-in-hand for help integrating that transport through libuv.

Kenton Varda

unread,
Feb 24, 2015, 7:06:29 PM2/24/15
to Tim Popham, capnproto
On Mon, Feb 23, 2015 at 11:19 PM, Tim Popham <impo...@gmail.com> wrote:
On the server<->server case, I'll probably be back hat-in-hand for help integrating that transport through libuv.

The existing node-capnp bindings have code to integrate KJ with libuv.

But we haven't defined a good public-internet transport yet. I've been tinkering with something based on nacl and udp with 0-RT introductions and NAT traversal, which I mentioned before. Will probably write up a proposal in the next few weeks.

-Kenton
Reply all
Reply to author
Forward
0 new messages