In a recent article titled First Quantum Resistant Mesh VPN, we talked about how quantum computers could break the security of private networks. We also looked into how to make WireGuard-based networks safer by using pre-shared keys (PSK) generated by Rosenpass. Lastly, we learned how to set up a quantum-resistant point-to-point network in just a few minutes with NetBird.
If you are curious about how all this works and how we integrated Rosenpass into NetBird, this post is for you. As usual, the code is open source and you can check it for yourself on GitHub: netbird/netbirdio.
Ronsenpass Go implementation
The first challenge we had to overcome was using Rosenpass in our NetBird client application, which runs on your machines. The Rosenpass daemon that does the pre-shared symmetric key exchange is a standalone application written in Rust. In contrast, NetBird is built using Go. As we wanted to avoid complicating the installation process and packaging another tool with our NetBird, we had to find a way to embed Rosenpass.
Fortunately, we discovered a recent Go-based implementation of the Rosenpass protocol by cunicu. After collaborating with the project maintainer, we got a fully embeddable Go API. The following code starts an instance of the Rosenpass server and configures a handler function that is triggered when a newly negotiated PSK is available:
Brilliant! That was an easy one. Kudos to stv0g for the rosenpass-go library.
But that is not it. We still need to configure a pair of Rosenpass servers running on different machines to be aware of each other. How can we do this?
Automatic Rosenpass peer discovery
Besides generating a private and public key pair and setting a listen port, the Rosenpass configuration involves extra steps. Like WireGuard, peers using Rosenpass must know each other's public keys. Once again, luck is on our side; we can utilize the signalling service NetBird peers already use to negotiate direct WireGuard connections.
When the NetBird Management service authorizes both machines to establish a connection, the machines initiate a connection negotiation process. They collect a list of all potential connection candidates, including local and public IP addresses (IP: port). Using the Signal service, they exchange these candidates with each other and then proceed to ping these candidates to determine the best available option. The image below illustrates the process:

Extending the Signal service protocol to include a Rosenpass key did the trick. Our machines can now exchange their public keys and add them to the embedded Rosenpass instance.
We are missing one more step: setting up a remote Rosenpass endpoint.
Discovering Rosenpass endpoint
Initiating the key exchange process requires Rosenpass peers to be mutually accessible. To achieve this, it's necessary to discover and set up an endpoint (IP: port) on at least one of the peers. This part is trickier. So, what are our options?
We could start the same connection negotiation process, or NAT traversal, that we do for WireGuard connections. This involves using the Signal service mentioned earlier and pinging peers until they find a way through NAT. While it's feasible, it's not always straightforward. Hard NATs can make it difficult to set up a direct connection. We might have to use relay services to route encrypted peer-to-peer traffic between machines. Implementing this could get complicated and time-consuming. So, we had to make some compromises.
We chose a simpler method using the already established WireGuard connection between the machines. This strategy involved using the private IPs assigned by NetBird to set up the Rosenpass endpoints. Our process was to allow the standard NetBird connection negotiation to conclude, and then, we configured Rosenpass to work through the established WireGuard tunnel.
To implement this solution, we only needed to add a new callback function to inform the rest of the software once the peers had established a connection. This function configures the Rosenpass endpoint to point to the remote peer using the NetBird's private IP. And once Rosenpass is done with the PSK exchange, we can apply it to WireGuard. Schematically, the Rosenpass configuration process looks like this:

Good! But are there any disadvantages to this approach?
Final adjustments
The issue arises from the moment after establishing a WireGuard connection and before the Rosenpass instances negotiate a PSK. During this time, which lasts for a few seconds, WireGuard operates without a configured PSK. Additionally, WireGuard's handshake mechanism keeps the connection session without PSK active for several minutes, even if we set up the PSK immediately after the handshake. This is how WireGuard works, meaning that our connection is not quantum-secure for around two minutes—a scenario we found unacceptable!
We opted for a straightforward solution to tackle this issue: autogenerating a PSK before and restarting the WireGuard session. Initially, prior to initiating the WireGuard connection negotiation process, the agent generates a pre-shared key. This key is derived using its public key and the public key of the remote peer. It's important to note that Rosenpass does not participate in this key generation process. Below is a sample code snippet illustrating this procedure:
The function combines the first 16 bytes from each public key, creating a concatenated byte array that serves as input to the function, generating a new key. This process ensures that, since both peers know each other's public keys, they can consistently regenerate the same PSK.
This method is initially sufficient, employing an "interim" PSK for the preliminary phase of the connection. This temporary PSK remains in use for only a short duration—until Rosenpass successfully negotiates a PSK.
Following Rosenpass's successful negotiation, the agent refreshes the WireGuard session. This refresh is executed by removing the WireGuard peer from the interface and then re-adding it—this time applying the newly negotiated PSK. The entire operation is swift, taking merely a few milliseconds, and has a minor impact on the connection.
That's the whole solution. Rosenpass will negotiate a new PSK every two minutes for each peer, and NetBird will apply these keys to the WireGuard interface, making point-to-point connections quantum-resistant. Here is the integration code that we open sourced on GitHub: netbirdio/netbird
Backwards compatibility
If both peers support and enable the Rosenpass feature, they will establish a post-quantum secure connection. However, if one of the peers do not enable this feature or run an older version that lacks Rosenpass, the connection won't work. We intentionally did this to enforce quantum resistance so the network is either fully quantum-resistant or not. A warning indicating that will pop up when running the command:
It may not be convenient to have some connections working and some not, especially with large deployments. Therefore, we introduced a flag . In this case, the NetBird client will default to a standard WireGuard connection without pre-shared keys for those connections that don't support Rosenpass. It will continue negotiating PSKs with Rosenpass for the rest, ensuring enhanced security wherever possible:
Conclusion
Initially, the WireGuard connection uses a PSK generated from the peers' public keys for a few seconds, an acceptable limitation. Moreover, when NetBird peers connect to the NetBird Signal and Management services, these connections do not offer quantum resistance, making them potentially vulnerable to attacks.
However, this is not to say improvements cannot be made. We are committed to enhancing our system, aiming to secure all communications with quantum-resistant measures in the future.
Meanwhile, if you want to try NetBird with Rosenpass for yourself, install it and run on a couple of machines. This won't take more than five minutes of your time:
