Hi, Has anyone thought about a standardized WireGuard IPv6 ULA generated = from the PublicKey ? WireGuard Unique Identifier (WUI) allowing a host to assign iteslf a = unique 64-Bit IPv6 interface identifier (WUI-64) ? Possibly picking off bits of a hash of the PublicKey ? For mass deployments, could this be a useful standard ? Lonnie
-----BEGIN PGP SIGNED MESSAGE----- Hash: SHA256 On 04/12/17 16:52, Lonnie Abelbeck wrote: > Hi, > > Has anyone thought about a standardized WireGuard IPv6 ULA > generated from the PublicKey ? > > WireGuard Unique Identifier (WUI) allowing a host to assign iteslf > a unique 64-Bit IPv6 interface identifier (WUI-64) ? > > Possibly picking off bits of a hash of the PublicKey ? > > For mass deployments, could this be a useful standard ? Mass deployments are likely to use some form of centralised and automated configuration management/provisioning solution already, which can take care of performing the calculations and assignments you are suggesting. Regards, Aaron Jones -----BEGIN PGP SIGNATURE----- Version: GnuPG v2 Comment: Using GnuPG with Thunderbird - http://www.enigmail.net/ iQIcBAEBCAAGBQJaJYJsAAoJEIrwc3SIqzASaAYP/RJMae43IXsvgtJd33/awnPD Aam6tUWHgISo2SeM3syrXNr1kIpTTca4PwyLfm5w8sxy/Zw0RF0Ux1lx3PH9NAd4 OT0f+UR9jpzLmncUS1t3I5z2g398kWr3Z5Fud1y8kvWgjbhp1dAjRv1FOwzXeQqM Zfk9it3qBVNpu3bzuXwNimT13Z3uYXh6BCyI++gyui9glq2eOUoGRQv5Rw1pXnkY 2VthV9pnJVjn5crvSyNw5Y0SZisE5BnDxVvy72sXTO//Hx6Gg7bnPYt/2mNOQTNG KUL2r7XJ4zm2IdqohOfSmjvCnlkh1KEg9ZPbrrrzSammwPVuMxCxKcy6GG+ETGSn yGj4gIYAW1x3HjbhG+1j0jTAh3jelayp4iHMf0gaFo2KY8jxbzXnBRAFiGGKJxIk mdIBI64Nog9lIm/hI3wNxkFGKfqMqDP9qVG3jPj2b4ACFLvsy4B8rySc2PjZIaKv D+MfSeiB8culswELDgIZH1U4SRks6Kec5qV81wkP/1t1g2t9zspc3rnBahGvMhgs lNWLfeFADe5bR7wqeBDzx52XvhvrGWqRD/eJney7THLQj05CVTB0cikPJ25AFnNA FfzThVEDIsGrGbzSMAq82Brj6bAs81F8Vz/Lnrmba8ejLsI0QsOjNfncEzte1VBz 4cbh8sSBh+udO4cItEM3 =Nvi/ -----END PGP SIGNATURE-----
On Mon, 4 Dec 2017 10:52:28 -0600
Lonnie Abelbeck <lists@lonnie.abelbeck.com> wrote:
> Hi,
>
> Has anyone thought about a standardized WireGuard IPv6 ULA generated
> from the PublicKey ?
This was indeed already discussed, albeit not for ULAs, but link-local
addresses (fe80::/64). IIRC, Jason rejected it citing the KISS
principle -- and I fully agree with that. Adding a hundred small
features useful for certain corner cases is a sure way to transform
wireguard into a behemoth of ipsec/openvpn dimensions. :)
Cheers,
Luis Ressel
Hey, >> Has anyone thought about a standardized WireGuard IPv6 ULA generated >> from the PublicKey ? > >This was indeed already discussed, albeit not for ULAs, but link-local >addresses (fe80::/64). IIRC, Jason rejected it citing the KISS >principle -- and I fully agree with that. Adding a hundred small >features useful for certain corner cases is a sure way to transform >wireguard into a behemoth of ipsec/openvpn dimensions. :) > >https://lists.zx2c4.com/pipermail/wireguard/2017-April/001177.html Was this really a rejection? I'd like to join the "poking and prodding about supporting IPv6 Link Local addresses". Deriving a IPv6 link-local address from the pubkey and adding it to the interface should be a no-brainer and sane default, and already fix Babel Routing (and most other issues) for "point-to-point tunnels" (only one peer, both sides set AllowedIPs=::/0). Quoting the proposal from the linked email: > # wg set wg0 llv6 on > > This command fails and returns -ENOTUNIQ if two existing peers have > the same value of hash(pubkey). When this command succeeds:, the wg0 > interface receives an automatically assigned IP address of > fe80::hash(interfacepubkey)/64. Every peer has > fe80::hash(peerpubkey)/128 implicitly added to their allowed-ips. When > adding a new peer, if hash(pubkey) is the same value of an existing > peer, the command fails and returns -ENOTUNIQ. Of course, generating these addresses could be implemented into downstream tooling, but I'd rather see this defined in wireguard itself. Then we'd not have multiple implementations possibly using different hashes. A standardized hashing, adding fe80::hash(interfacepubkey)/64 and AllowedIPs=fe80::hash(peerpubkey)/128 for each peer would also allow bring instant IPv6 connectivity between peers - which I find quite appealing. I'd propose handling the multicast replication ideas as well as the ULA address generation as a followup, or the business of higher-level tooling. Regards, Florian
On Wed, 24 Jun 2020 at 17:37, Florian Klink <flokli@flokli.de> wrote:
> Deriving an IPv6 link-local address from the pubkey and adding it to the
> interface should be a no-brainer and sane default, and already fix Babel
> Routing (and most other issues) for "point-to-point tunnels"
> (only one peer, both sides set AllowedIPs=::/0).
An idea to implement as an option for e.g. wg-quick, rather than the
base code-base itself?
--
Chriztoffer
IMO link-local should be assigned by core - RFC4291 which is defining IPv6 clearly says: "Its required Link-Local address for each interface." https://tools.ietf.org/html/rfc4291#section-2.8 On 6/24/20 7:08 PM, Chriztoffer Hansen wrote: > On Wed, 24 Jun 2020 at 17:37, Florian Klink <flokli@flokli.de> wrote: >> Deriving an IPv6 link-local address from the pubkey and adding it to the >> interface should be a no-brainer and sane default, and already fix Babel >> Routing (and most other issues) for "point-to-point tunnels" >> (only one peer, both sides set AllowedIPs=::/0). > An idea to implement as an option for e.g. wg-quick, rather than the > base code-base itself? >
I've been working on this a bit from a completely independent perspective: bootstrapping embedded systems which have a persistent keypair, but no persistent storage for stuff like `AllowedIPs` assignments. In my usecase, the by-convention assignment of an IPv6 link-local address to each WireGuard peer allows a gossip-style protocol to update a newly-joined node with a (signed) set of configuration parameters, including the `AllowedIPs` entries that enable more comprehensive communication. The fact that the assignment of cryptographically-bound IPv6 LLAs has independently occurred to multiple parties now is not lost on me -- it's usually a sign of good design! I also agree that this type of thing makes a lot more sense in the context of `wg-quick` than the kernel module or `wg` tools themselves. However, care should be taken to make sure that all potential implementations can adopt it without extra overhead. For this reason, I'm biased towards simplicity in the specification, not necessarily simplicity of implementation as part of `wg-quick`. I would caution that the decision of how to generate and assign addresses from public keys should be treated as a layer-3 problem. Each IPv6 network device is *required* to have a link-local address by the RFC -- even if you can get away without one in practice this makes it clear that the proper conceptual home of LLA assignment is in the realm of bits and bytes, rather than strings and pipes -- even if its appropriate place in the architecture of the *reference implementation* is in a optional shell script. One more point of clarification: ULAs are in the `fc00::/7` space, while LLAs are in `fe80::/10`. LLAs are what we want, because they are explicitly interface-scoped -- and that means that they can be counted on to be always be bound to the peer, no matter what the specific network configuration of the node might be. Sending a packet to `fe80::dead:beef%wg0` will always refer to a specific peer on the `wg0` interface, and provides a guarantee that the contents of that packet will be transmitted securely; whereas sending to `fc00::dead:beef` *might* be on the `wg0` interface, but to be sure you'd have to know that you didn't have a route to that address via any other interface. This might be true on some -- or most -- nodes, but it's not something that can be assumed. This makes cryptographically-derived ULAs much less useful than cryptographically-*bound* LLAs. # OK, so how do we do it? The general idea of using a hash to generate an IPv6 LLA is fairly straightforward (and obvious, given that several people have come up with it independently), but there are still some points that require standardization. I think I have an exhaustive list of the points of divergence that must be addressed; I will discuss each of them and my perspective. ## What netmask should be used? **fe80::/10.** The IPv6 RFCs separate the address into a subnet and interface identifier, which would seem to indicate that something like `fe80::/64` should be used instead; however, by their very nature link-local addresses are not part of a subnet. In addition, it is desirable that each address be bound as strongly as possible to the key it is derived from -- 118-bit security is a lot closer to 128-bit than 64-bit security. ## Should the subnet identifier be concatenated with the results of the hash, or should leading bits of the hash be dropped? **(SUBNET & MASK) | (HASH & ~MASK)** Binary math is good, cheap, and obvious, whereas concatenation is only straightforward if the netmask is a whole number of bytes. Otherwise you have to bitshift everything and it just gets messy. Besides, it's a net*mask* -- seems like you should use it to *mask* things. ## Should the hash be taken over the key itself, or the Base64 encoding of the key? **The key itself.** While the tools are fairly consistent in the use of the Base64 encoding in user-facing scenarios, it's important to consider that there's nothing fundamental about the WireGuard protocol itself that requires the use of Base64 anywhere. I argue that it would be inappropriate to introduce a dependency on it at such a low level -- especially since you can just do `base64 -d` inside `wg-quick`. ## What algorithm should the hash be done with? **Blake2s with 32 bytes of output**. This is simply the `HASH()` function in the WireGuard protocol specification, and I think that using the same hash function as the Noise construction makes a lot of sense. Even though output length is a tunable parameter of the Blake2s function and an LLA will never use more than 16 bytes, I feel that being consistent and obvious is important. (Also, note that Blake2 tunes output length by truncation internally; the only difference between taking a 16- or 32-byte long digest is flipping a couple of bits during the setup phase. The performance characteristics are exactly the same.) That said, most of the attempts at implementing a IPv6 LLA assignment scheme I've seen simply depend on `sha256sum` and call it a day, because there's not a widespread CLI tool that does Blake2s for you. There *are* a couple of different tools named `b2sum` -- the one made by the Blake2 authors is fine, but the identically-named GNU coreutils utility, which most people will get if they install their distro's `b2sum` package, only does Blake2b (and takes a different set of flags to boot). Still, like I mentioned above, we should be looking at this from a protocol point of view, and requiring a whole extra crypto primitive just for calculating an LLA seems wasteful. Implementing WireGuard already requires that the Blake2s hash be available, and that it's not easily accessible by the wg-quick tool is simply an unfortunate quirk of the reference implementation. Think about a constrained environment like a microcontroller -- SHA256 isn't a simple algorithm, and it would probably cause a 50% increase in code size. Luckily, Blake2s is a simple and elegant algorithm, and in an effort to get some working code out there I've [implemented][1] it in ~100 lines of Bash script. (It's gotta be Bash because it needs array support, but that's what `wg-quick` uses anyway.) It's slow compared to a typical implementation, but it's not like we're mining cryptocurrency here, and because WireGuard public keys are of a known, fixed length the input will never be longer than a single block. (Single-block hashes benchmark at around 50ms on my system, just for reference.) I hope this helps accelerate the project, but I can understand that a shell implementation might seem too janky for long-term use: a potential solution would be to integrate the LLA calculation into the wg tool, in a similar fashion to how the Curve25519 public key calculation is handed by `wg pubkey`. I'm imagining a `wg lla` command which takes in a Base64-encoded public key and spits out a string of the form `fe8b:5ea9:9e65:3bc2:b593:db41:30d1:0a4e` (which happens to be the LLA associated with an all-zero public key under my proposed scheme). [1]: https://gist.github.com/reidrankin/3a39210ce437680f5cf1ac549fd1f1ff --Reid On Wed, Jun 24, 2020 at 1:11 PM Chriztoffer Hansen <ch@ntrv.dk> wrote: > > On Wed, 24 Jun 2020 at 17:37, Florian Klink <flokli@flokli.de> wrote: > > Deriving an IPv6 link-local address from the pubkey and adding it to the > > interface should be a no-brainer and sane default, and already fix Babel > > Routing (and most other issues) for "point-to-point tunnels" > > (only one peer, both sides set AllowedIPs=::/0). > > An idea to implement as an option for e.g. wg-quick, rather than the > base code-base itself? > > -- > > Chriztoffer
On L, 2020-06-27 at 17:43 -0400, Reid Rankin wrote: > Luckily, Blake2s is a simple and elegant algorithm, and in an effort > to get some working code out there I've [implemented][1] it in ~100 > lines of Bash script. It turns out that Python includes blake2s implementation that seems to work with default arguments. So it's possible to implement this IPv6 address calculation algorithm in 7 lines. https://gist.github.com/artizirk/c91e4f8c237dec07e3ad1b286f1855a7
I've been using something similar for ORCHIDv2-ish addressing, q.v. [1]. from base64 import b64decode from hashlib import shake_128 from ipaddress import IPv6Network public_key = b64decode(...) secret = "somesecret".encode('utf-8') network = IPv6Network("2001:20::/28") hash = shake_128(secret + public_key).digest(network.max_prefixlen//8) mask = int.from_bytes(network.hostmask.packed, byteorder='big') host = int.from_bytes(hash, byteorder='big') addr = network[host & mask] The use of secret is optional but allows one to mix the addresses based on a shared secret. Substituting the link local range for the ORCHIDv2 range above should produce results similar to what you're getting. One thing to note, it's worth checking to see if the algorithm generates the network or broadcast addresses and either failing or shifting. (I'm considering adding a +1 or -1 based on whether we hit said address to the above; the real code just asserts right now.) ~Derrick [1] https://github.com/pallas/wgnlpy/commit/5c1f4bf876b39bad29135370e5f297e305dab840 On 6/28/20 3:15 AM, Arti Zirk wrote: > On L, 2020-06-27 at 17:43 -0400, Reid Rankin wrote: >> Luckily, Blake2s is a simple and elegant algorithm, and in an effort >> to get some working code out there I've [implemented][1] it in ~100 >> lines of Bash script. > It turns out that Python includes blake2s implementation that seems to > work with default arguments. So it's possible to implement this IPv6 > address calculation algorithm in 7 lines. > > https://gist.github.com/artizirk/c91e4f8c237dec07e3ad1b286f1855a7 >
Reid Rankin <reidrankin@gmail.com> writes:
> Each IPv6 network device is *required* to have a link-local
> address by the RFC
Given this, and how obvious it is to just hash the pubkey into a LL
address, I think the right thing to do would just be to take the hashing
scheme you proposed and put it into the wg kernel part, on by default.
Maybe with a switch to turn it back off for the paranoid :)
-Toke
On Mon, 29 Jun 2020 12:22:49 +0200
Toke Høiland-Jørgensen <toke@toke.dk> wrote:
> Reid Rankin <reidrankin@gmail.com> writes:
>
> > Each IPv6 network device is *required* to have a link-local
> > address by the RFC
>
> Given this
What you quoted is the shakiest statement of the entire proposal. Might be a
cool idea and all, but I don't think RFCs say anything about "requiring" that
for point-to-point L3 interfaces, where there's no functioning multicast or
broadcast to begin with. And it doesn't seem nice that submitter is trying to
skew facts in their favor like that.
--
With respect,
Roman
I'm assigning fe80 addresses derived from device MAC addresses for my own Babel + WireGuard use case.
To chip in on this I don't think WireGuard should add any 'auto-magical' behavior into it's core code. There are significant advantages to keeping core WireGuard ultra lean and pushing complexity into configuration scripts and separate programs.
That being said adding an fe80 address as a standard for setup scripts (wg-quick) seems like a good idea.
--
Justin Kilpatrick
justin@althea.net
On Mon, Jun 29, 2020, at 6:31 AM, Roman Mamedov wrote:
> On Mon, 29 Jun 2020 12:22:49 +0200
> Toke Høiland-Jørgensen <toke@toke.dk> wrote:
>
> > Reid Rankin <reidrankin@gmail.com> writes:
> >
> > > Each IPv6 network device is *required* to have a link-local
> > > address by the RFC
> >
> > Given this
>
> What you quoted is the shakiest statement of the entire proposal. Might be a
> cool idea and all, but I don't think RFCs say anything about "requiring" that
> for point-to-point L3 interfaces, where there's no functioning multicast or
> broadcast to begin with. And it doesn't seem nice that submitter is trying to
> skew facts in their favor like that.
>
> --
> With respect,
> Roman
>
Roman Mamedov <rm@romanrm.net> writes:
> On Mon, 29 Jun 2020 12:22:49 +0200
> Toke Høiland-Jørgensen <toke@toke.dk> wrote:
>
>> Reid Rankin <reidrankin@gmail.com> writes:
>>
>> > Each IPv6 network device is *required* to have a link-local
>> > address by the RFC
>>
>> Given this
>
> What you quoted is the shakiest statement of the entire proposal. Might be a
> cool idea and all, but I don't think RFCs say anything about "requiring" that
> for point-to-point L3 interfaces, where there's no functioning multicast or
> broadcast to begin with. And it doesn't seem nice that submitter is trying to
> skew facts in their favor like that.
Eh? This is specified pretty clearly in RFC4291, section 2.1:
2.1. Addressing Model
IPv6 addresses of all types are assigned to interfaces, not nodes.
An IPv6 unicast address refers to a single interface. Since each
interface belongs to a single node, any of that node's interfaces'
unicast addresses may be used as an identifier for the node.
All interfaces are required to have at least one Link-Local unicast
address (see Section 2.8 for additional required addresses). A
single interface may also have multiple IPv6 addresses of any type
(unicast, anycast, and multicast) or scope. Unicast addresses with a
scope greater than link-scope are not needed for interfaces that are
not used as the origin or destination of any IPv6 packets to or from
non-neighbors. This is sometimes convenient for point-to-point
interfaces. There is one exception to this addressing model:
A unicast address or a set of unicast addresses may be assigned to
multiple physical interfaces if the implementation treats the
multiple physical interfaces as one interface when presenting it
to the internet layer. This is useful for load-sharing over
multiple physical interfaces.
Currently, IPv6 continues the IPv4 model in that a subnet prefix is
associated with one link. Multiple subnet prefixes may be assigned
to the same link.
The fact that Wireguard doesn't assign one is often a source of
annoyance, and since there already is a unique identifier for each peer
on a link (the public key), I really don't see why wg shouldn't just
assign a LL identifier and be done with it. Sure, have a config knob to
turn it off if you're not using IPv6, but let's make this the default
and have wg devices 'just work' over IPv6 by default.
-Toke
On Mon, 29 Jun 2020 13:03:40 +0200
Toke Høiland-Jørgensen <toke@toke.dk> wrote:
> Eh? This is specified pretty clearly in RFC4291, section 2.1:
It also says:
-----
2.5.6. Link-Local IPv6 Unicast Addresses
Link-Local addresses are for use on a single link. Link-Local
addresses have the following format:
| 10 |
| bits | 54 bits | 64 bits |
+----------+-------------------------+----------------------------+
|1111111010| 0 | interface ID |
+----------+-------------------------+----------------------------+
-----
So should we also follow the designated format for link-locals, or accept that
WG's case differs from what they had in mind in those sections. That the
"interface" is a special one, with a "link" that doesn't function as other
kinds of links do, that there's no "neighbour" per se to contact by an
all-neighbour multicast for instance, no mechanism for the "all routers"
multicast to work, etc (i.e. all of what the LLs were intended to support).
To be clear I'm not against adding LLs, just that "the RFC says so" shouldn't
be considered the main argument for that when it comes to WG.
--
With respect,
Roman
Roman Mamedov <rm@romanrm.net> writes: > On Mon, 29 Jun 2020 13:03:40 +0200 > Toke Høiland-Jørgensen <toke@toke.dk> wrote: > >> Eh? This is specified pretty clearly in RFC4291, section 2.1: > > It also says: > > ----- > > 2.5.6. Link-Local IPv6 Unicast Addresses > > Link-Local addresses are for use on a single link. Link-Local > addresses have the following format: > > | 10 | > | bits | 54 bits | 64 bits | > +----------+-------------------------+----------------------------+ > |1111111010| 0 | interface ID | > +----------+-------------------------+----------------------------+ > > > ----- > > So should we also follow the designated format for link-locals, or > accept that WG's case differs from what they had in mind in those > sections. In general I'd say that deviating from the RFC needs a good reason. Expanding the number of bits we can use for the identifier may be a good reason to expand the LL interface ID width (although I'm not actually too worried about collisions even if we only use 64 bits). I have yet to hear a good reason for not just having LL addresses enabled by default :) > That the "interface" is a special one, with a "link" that doesn't > function as other kinds of links do, that there's no "neighbour" per > se to contact by an all-neighbour multicast for instance, no mechanism > for the "all routers" multicast to work, etc (i.e. all of what the LLs > were intended to support). But it's not special. If wireguard is setup as a single point-to-point link (allowed-ip ::/0), "all-neighbour multicast" and "broadcast" already works. If there are multiple peers on the interface it doesn't, but that is also a bug that should be fixed as far as I'm concerned. > To be clear I'm not against adding LLs, just that "the RFC says so" > shouldn't be considered the main argument for that when it comes to > WG. Oh sure, to me the main argument is also "because it's useful" rather than "because the RFC says so". But in my view "because it's useful" is also the reason that requirement is in the RFC in the first place, so really it amounts to the same thing. -Toke
On E, 2020-06-29 at 14:15 +0200, Toke Høiland-Jørgensen wrote: > In general I'd say that deviating from the RFC needs a good reason. > Expanding the number of bits we can use for the identifier may be a > good reason to expand the LL interface ID width (although I'm not > actually too worried about collisions even if we only use 64 bits). Few more counter arguments against expanding identifier length: 1. There is a rejected errata 4406 that wants to do this https://www.rfc-editor.org/errata/eid4406 2. FreeBSD and probably other *BSD/macOS use those unused 56 bits to store the link scope_id. And support nonstandard fe80:1::30/64 notation instead of fe80::30%1/64 to specify the scope. https://stackoverflow.com/a/5891805/2303328 https://github.com/freebsd/freebsd/blob/76f9308e3e2b80e95630efcdd994f3c133806bf4/share/doc/IPv6/IMPLEMENTATION#L427 https://github.com/freebsd/freebsd/blob/e9a39e0c3c22543812afd4de74d1d0ad6782100b/sys/netinet6/scope6.c#L363
Hi folks, We're probably not going to do this, for two reasons: 1. The security model of hashing keys down to tiny hash lengths is dubious, and opens us up to all manner of interesting collision attacks. Cryptkey routing implies a strong binding between IP and pubkey. A hash with collisions means a weak binding. 2. There is very little practical utility. In WireGuard, both sides must _already_ preshare their public keys, and there's no way around this. So, at the same time that they preshare their public keys, they can also exchange randomly generated LL or ULA addresses. (Notably, this is how wg-dynamic works.) In other words, both sides are already required to know 32 bytes about each other in order to communicate; tagging on an additional 16 to whatever mechanism exchanges those 32 should not be a problem anywhere. Trying to shave off 16 bytes of an initial communications setup by adding complicated hashing schemes and collision issues seems like not good decision making. Jason
It simply does not make sense to set ULA automatically. ULA fc00::/7 is subdivided in fc00::/8 and fd00::/8. The former would use some global registry while the second one uses a random 40-bit subnet prefix (to avoid conflicts). You would need to share this 40-bit random value with every node in order to have a common /48 prefix. Besides that, It would not make sense to have a full /48 for a single VPN network. It should be subdivided into common /64 appending another 16-bit subnet id. So, in the end, you would need a way to pass the ULA prefix (40-bit + 16-bit). ULA should be configured just like any other global address. If ULA is not the topic, we might need to change the thread name. The issue here is really about having Link Local Address automatically set. I know that Wireguard wants to be as clean as possible not including any automatic setup logic. However, most protocols that would do that job, at least, expect LL to be present (DHCPv6). LL is set by the kernel for ethernet devices but not for TUN interfaces, probably because there is simply no "link" on a L3 interface. There is, for example, a request to have it set for OpenVPN (https://community.openvpn.net/openvpn/ticket/415). I would expect IPv6 LL address to be present in any default scenario. I just don't know what would be the one to set it up. For ethernet devices, it is the kernel itself. For wireguard, there is a shared responsibility between userland (wg and wg-quick) and kernel. However, as a required feature, I would not depend on any software that is not required. If wg-quick is optional, it would not be the place to set the LL address. Maybe wg would be the one to set it as soon as a IPv6 stack is up for a wireguard interface, even when there is no intention to use IPv6. However, if wg is also "optional", it would be nice if the kernel API could require the userland code to inform the interface-id (or set a link local address) before IPv6 is up. For wireguard, LL address setup also means to set allowed IPs automatically. Regarding what interface id should be used, even random value is acceptable but not ideal for management as it could be used as part of device ID. Wireguard could use a simple algorithm to map pubkey 256-bit into a 64-bit value, just like ethernet 48-bit is mapped to a 64-bit value. It can be as simple as getting the last 64-bit from pubkey or any already in use form of hash. Keep it as simple as possible. It is not expected to be secret. The privacy extension, if used, is for automatically generated global addresses, not link-local one. Keep in mind that LL is not expected to be a replacement for any global address (ULA or Internet one). They should still be set. At least for Linux, no "normal" process would really use LL addresses without specifying the outgoing interface ("fe80::...%interface"), which might limit what you can do with it. In summary, my suggestions: 1) LL address should be set automatically by wg, better if required by kernel interface or even set up by kernel module. 2) interface identification can be derived from pubkey with a simple algorithm. It does not need to be a secure hash. Regards, --- Luiz Angelo Daros de Luca luizluca@gmail.com
> 2. There is very little practical utility. In WireGuard, both sides
> must _already_ preshare their public keys, and there's no way around
> this.
Well, it looks like you've discovered the method behind my madness!
Specifically, while a handshake *initiator* must know the public key
of the responder it's trying to talk to, the *responder* doesn't need
to know anything about the initiator ahead of time -- because the
initiator's public key is right there in the handshake. In my usecase,
I examine incoming handshake requests in a userspace daemon via
nfqueue. The daemon knows the interface private key, so it can also
see the initiator's public key, and if it's a new peer the daemon adds
it via `wg set` -- with only the calculated LLA in the `AllowedIPs`
list -- before releasing the handshake request for delivery. The
newly-minted peer can then send a certificate via TFTP (a very simple,
DoS-resistant protocol) to the responder's LLA, which convinces the
responder to add additional stuff to the initiator's `AllowedIPs`
list. Because this bootstrap process occurs within the tunnel,
integrity and confidentiality protection are already assured -- and
WireGuard is already ensuring that the node with the initiator's LLA
possesses the initiator's private key.
Now, I'm not recommending this specific scheme generally, but it goes
to show that there is indeed a benefit to calculating an LLA via a
hash -- it allows you to move an out-of-band authentication ceremony
in-band. Maybe you're not using my fancy nfqueue setup to pull out
public keys from handshakes -- you could just have a web server with a
POST handler that takes unknown public keys and adds them as peers.
You're not trusting anything by doing this if you're using a hashed
LLA, and then you could proceed to chat inside the tunnel to run
whatever authentication scheme you'd like. But if you're taking both a
public key and an arbitrary LLA as input, you're going to have to
trust the assertion that that specific public key should be assigned
that specific address. To do that, you'll need to verify both that the
owner of the public key indeed wants that LLA (what if the request was
forged?) and that the owner of the key is authorized to use that LLA
(what if they're trying to steal or squat on someone else's address?),
and you'll have to do both of those things out-of-band -- because
until you settle on addressing you can't even talk to each other
inside the tunnel.
Anyhow, my point is that pre-sharing public keys might be easy, but
sharing *relationships* between public keys and addresses is a whole
different ball of wax, requiring at least an integrity-protected
transport -- which would be a shame to have to do out-of-band, since
we've already got one of those.
--Reid
On Mon, Jun 29, 2020 at 1:59 PM Reid Rankin <reidrankin@gmail.com> wrote: > Well, it looks like you've discovered the method behind my madness! > Specifically, while a handshake *initiator* must know the public key > of the responder it's trying to talk to, the *responder* doesn't need > to know anything about the initiator ahead of time -- because the > initiator's public key is right there in the handshake. Fun fact: initial versions of WireGuard from years ago weren't like this. We wound up redoing some crypto and coming up with the `_psk2` variant for this purpose. I'm glad it's useful. I'm interested to learn: what are you doing this for? Got any code online? > In my usecase, > I examine incoming handshake requests in a userspace daemon via > nfqueue. The daemon knows the interface private key, so it can also > see the initiator's public key, and if it's a new peer the daemon adds > it via `wg set` -- with only the calculated LLA in the `AllowedIPs` > list -- before releasing the handshake request for delivery. The > newly-minted peer can then send a certificate via TFTP (a very simple, > DoS-resistant protocol) to the responder's LLA, which convinces the > responder to add additional stuff to the initiator's `AllowedIPs` > list. Because this bootstrap process occurs within the tunnel, > integrity and confidentiality protection are already assured -- and > WireGuard is already ensuring that the node with the initiator's LLA > possesses the initiator's private key. This sounds like a motivation for doing the LLv6 generation inside of your daemon, not inside of the kernel, right? In that case, your design must already take into account a malicious peer finding public key collisions after hashing. Perhaps you have some PRF situation? Or something else? Either way, this doesn't sound like something for core-wireguard, but a nice and novel thing you're building on top, sort of like wg-dynamic, which can happily exist in userspace, where the security of your design can be validated as one unit. Jason
> Fun fact: initial versions of WireGuard from years ago weren't like > this. We wound up redoing some crypto and coming up with the `_psk2` > variant for this purpose. I'm glad it's useful. I'm interested to > learn: what are you doing this for? Got any code online? That's a dangerous question to ask, because I'm really excited about it! It's an embedded device that connects otherwise-insecure stuff together into a transparent overlay network that's centrally configurable. You get a set of them in a box, plug one legacy thingamabob into each, and all the devices show up in a GUI. You configure all the IP allocations and allowed traffic flows, and an included hardware token signs the configuration. You then throw the master key into a safe somewhere, confident in the knowledge that even if your network infrastructure is all broken into by nation-state-du-jour your traffic will stay confidential; even if the network you're using to communicate is re-addressed underneath you -- or your stuff is moved across the country -- none of your legacy stuff will need reconfiguration; and even if the legacy stuff itself is broken into, the network configuration is being enforced by hardware. The boxes themselves have no persistent storage at all; in fact, possession of a private key the devices don't have is required to unlock the flash. The point is to resist malware infection by making assured remediation as simple as power-cycling the unit -- eventually, I'll have a dedicated microcontroller acting as a watchdog which shorts the reset pin to ground if the unit can't provide a TPM-backed health attestation every minute or so. The grand master plan is that as soon as you hack in and try to run something interesting, it all resets and you're back out again. Of course, each unit can't be updated every time you need to add a new one, and that's where the LLAs and in-band authentication stuff comes in. New boxes use Zeroconf to find peers, after which they connect and present the certificate authorizing their `AllowedIPs` and appropriate firewall setup. My goal is for every packet that comes out of each device except for ICMP, DHCP, and (m)DNS to be a WireGuard packet, cutting the attack surface to the bone -- and until you present a valid certificate, the only thing allowed inside the tunnel is TFTP. Most of my code for this thing is all fairly hacky and environment-specific at the moment -- that `wg-lla.sh` Gist from before is the first real piece I've been able to clean up and open-source. It's a fairly big project, but there are some more sections that I'm fairly certain I'll end up releasing as well; for example, the mesh-routing setup might be useful to some people. The principle is that by "brute-forcing" the MAC1 field from handshake initiations against the static public keys of all known peers, you can figure out what peer (or, rather, peer's endpoint) to send the handshake and subsequent flow towards, which makes every node in the mesh a potential endpoint for any other peer in the mesh. There's also a microcontroller-compatible implementation of WireGuard I'm working on (though in the very early stages), targeted at the Cortex-M0 platform and written in purely `no_std`, `forbid(unsafe_code)` Rust. All this stuff integrates into one big product in the end, but I'm very open to hearing community feedback on which of these bits would be most useful to others -- I'll prioritize them. (And if you're really interested in any of it, it's all at least proof-of-concept, and I do contract work!) By the way, putting the PSK exchange at the end is useful for another reason, too: you can use it to chain authentication mechanisms. The key here is that the PSK can be updated after sending an initiation packet, but before receiving the response. I've done an experiment using nfqueue on the initiator to catch an outgoing handshake request and stick an extra nonce on the end -- which is signed using a secondary key. On the responder side, another nonce is chosen; a new PSK, calculated by hashing the nonces, is set using `wg`, and the initiator ID is noted. The handshake initiation is then released for processing, which occurs using the freshly-set PSK. When WireGuard sends the handshake response, nfqueue intercepts the outgoing packet, matches the initiator ID, and sticks the responder's nonce on the end encrypted to the secondary key. The initiator intercepts the response with nfqueue, decrypts the second nonce, calculates the new PSK, and issues the same `wg set` command before releasing the response packet. This all works just fine (at least, as long as the daemon stays up), proving that you can do interactive authentication out-of-band. I've since realized that sticking the authentication I'm looking for inside the tunnel is a much better choice for my application, but I'm glad to have options. > This sounds like a motivation for doing the LLv6 generation inside of > your daemon, not inside of the kernel, right? In that case, your > design must already take into account a malicious peer finding public > key collisions after hashing. I'm not actually looking for a feature here as much as I am a standard, and this definitely shouldn't go in the kernel. (Heck, I wrote a Blake2s implementation in Bash just so it wouldn't have to go any deeper than `wg-quick`.) That said, part of me would really like to see a command like `wg lla AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=` that spits out `fe8b:5ea9:9e65:3bc2:b593:db41:30d1:0a4e`. That would serve the dual purposes of avoiding running a hash algorithm in a shell script and serving as a standard. There's not a lot of decisions to make when you sit down with the goal to make an LLA from a public key hash -- I listed them in my prior post, and I'm pretty sure it's exhaustive -- and it would be a shame if we didn't have sensible defaults to adopt. As for security, a 256-bit ECC public key only gives 128 bits of security in the first place. Hashing the key down to 16 bytes doesn't hurt security, because it would take as much effort to find a collision as it would to just run Pollard-rho and crack the key you're trying to mess with. The compromise comes in when you start masking off bits, and losing 10 bits to fit into `fe80::/10` isn't actually that bad -- in fact, I argue that it's negligible, because finding a colliding keypair requires an ECC scalar multiplication to determine the public key associated with each private key guess. This easily takes more than 1024 times as long as running the hash itself, meaning that the process of finding a keypair that's a second-preimage of a desired 118-bit LLA suffix actually takes longer than brute-forcing a second-preimage of a 128-bit Blake2s hash. (For reference, Curve25519 takes [832457 cycles][1] for a single scalar multiplication; Blake2s on a single 64-byte block takes [5.5 cycles per byte][2], or 352 cycles. These numbers are different microarchitectures, so it's kind of an apples-to-oranges thing, but we're talking orders of magnitude here.) [1]: https://cr.yp.to/ecdh/curve25519-20051115.pdf [2]: https://blake2.net/blake2.pdf