From 45138bf6afcc4d96ac6e750742a890d180329924 Mon Sep 17 00:00:00 2001 From: Jeff Leung Date: Fri, 15 May 2026 04:53:44 +0000 Subject: [PATCH] Add Wireguard KeyGen Util --- .gitignore | 6 +++ AS1024.Wireguard.Utils.slnx | 5 ++ .../AS1024.Wireguard.Utils.csproj | 13 +++++ .../WireGuardKeyUtils.cs | 45 ++++++++++++++++++ .../AS1024.Wireguard.Utils.Tests.csproj | 25 ++++++++++ .../WireGuardKeyUtilsTests.cs | 31 ++++++++++++ .../AS1024.Wireguard.Utils.Cli.csproj | 14 ++++++ tools/AS1024.Wireguard.Utils.Cli/Program.cs | 47 +++++++++++++++++++ 8 files changed, 186 insertions(+) create mode 100644 .gitignore create mode 100644 AS1024.Wireguard.Utils.slnx create mode 100644 src/AS1024.Wireguard.Utils/AS1024.Wireguard.Utils.csproj create mode 100644 src/AS1024.Wireguard.Utils/WireGuardKeyUtils.cs create mode 100644 tests/AS1024.Wireguard.Utils.Tests/AS1024.Wireguard.Utils.Tests.csproj create mode 100644 tests/AS1024.Wireguard.Utils.Tests/WireGuardKeyUtilsTests.cs create mode 100644 tools/AS1024.Wireguard.Utils.Cli/AS1024.Wireguard.Utils.Cli.csproj create mode 100644 tools/AS1024.Wireguard.Utils.Cli/Program.cs diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..d1e22a0 --- /dev/null +++ b/.gitignore @@ -0,0 +1,6 @@ +bin/ +obj/ + + +# MCP Vector Search index directory +.mcp-vector-search/ diff --git a/AS1024.Wireguard.Utils.slnx b/AS1024.Wireguard.Utils.slnx new file mode 100644 index 0000000..5e84e03 --- /dev/null +++ b/AS1024.Wireguard.Utils.slnx @@ -0,0 +1,5 @@ + + + + + diff --git a/src/AS1024.Wireguard.Utils/AS1024.Wireguard.Utils.csproj b/src/AS1024.Wireguard.Utils/AS1024.Wireguard.Utils.csproj new file mode 100644 index 0000000..aee8cc6 --- /dev/null +++ b/src/AS1024.Wireguard.Utils/AS1024.Wireguard.Utils.csproj @@ -0,0 +1,13 @@ + + + + net10.0 + enable + enable + + + + + + + diff --git a/src/AS1024.Wireguard.Utils/WireGuardKeyUtils.cs b/src/AS1024.Wireguard.Utils/WireGuardKeyUtils.cs new file mode 100644 index 0000000..6178cf0 --- /dev/null +++ b/src/AS1024.Wireguard.Utils/WireGuardKeyUtils.cs @@ -0,0 +1,45 @@ +using System.Security.Cryptography; +using Org.BouncyCastle.Math.EC.Rfc7748; + +namespace AS1024.Wireguard.Utils; + +// WireGuard / Curve25519 keys. RFC 7748. +public static class WireGuardKeyUtils +{ + public const int KeySize = 32; + + public static byte[] GeneratePrivateKey() + { + var key = RandomNumberGenerator.GetBytes(KeySize); + Clamp(key); + return key; + } + + public static byte[] GetPublicKey(byte[] privateKey) + { + if (privateKey.Length != KeySize) + throw new ArgumentException($"WireGuard keys are {KeySize} bytes.", nameof(privateKey)); + + var scalar = (byte[])privateKey.Clone(); + Clamp(scalar); + + var pub = new byte[KeySize]; + X25519.ScalarMultBase(scalar, 0, pub, 0); + return pub; + } + + public static byte[] FromBase64(string s) + { + var key = Convert.FromBase64String(s); + if (key.Length != KeySize) + throw new ArgumentException($"WireGuard keys are {KeySize} bytes.", nameof(s)); + return key; + } + + static void Clamp(Span scalar) + { + // Curve25519 clamp, RFC 7748 §5. + scalar[0] &= 0xF8; + scalar[31] = (byte)((scalar[31] & 0x7F) | 0x40); + } +} diff --git a/tests/AS1024.Wireguard.Utils.Tests/AS1024.Wireguard.Utils.Tests.csproj b/tests/AS1024.Wireguard.Utils.Tests/AS1024.Wireguard.Utils.Tests.csproj new file mode 100644 index 0000000..35337f4 --- /dev/null +++ b/tests/AS1024.Wireguard.Utils.Tests/AS1024.Wireguard.Utils.Tests.csproj @@ -0,0 +1,25 @@ + + + + net10.0 + enable + enable + false + + + + + + + + + + + + + + + + + + diff --git a/tests/AS1024.Wireguard.Utils.Tests/WireGuardKeyUtilsTests.cs b/tests/AS1024.Wireguard.Utils.Tests/WireGuardKeyUtilsTests.cs new file mode 100644 index 0000000..1247cd5 --- /dev/null +++ b/tests/AS1024.Wireguard.Utils.Tests/WireGuardKeyUtilsTests.cs @@ -0,0 +1,31 @@ +namespace AS1024.Wireguard.Utils.Tests; + +public class WireGuardKeyUtilsTests +{ + [Fact] + public void RoundTrip() + { + var priv = WireGuardKeyUtils.GeneratePrivateKey(); + var pub = WireGuardKeyUtils.GetPublicKey(priv); + + var privB64 = Convert.ToBase64String(priv); + Assert.Equal(priv, WireGuardKeyUtils.FromBase64(privB64)); + Assert.Equal(WireGuardKeyUtils.KeySize, pub.Length); + } + + [Fact] + public void Rfc7748Vector() + { + // RFC 7748 §6.1. + var priv = Convert.FromHexString("77076d0a7318a57d3c16c17251b26645df4c2f87ebc0992ab177fba51db92c2a"); + var expected = Convert.FromHexString("8520f0098930a754748b7ddcb43ef75a0dbf3a0d26381af4eba4a98eaa9b4e6a"); + + Assert.Equal(expected, WireGuardKeyUtils.GetPublicKey(priv)); + } + + [Fact] + public void RejectsWrongLength() + { + Assert.Throws(() => WireGuardKeyUtils.GetPublicKey(new byte[31])); + } +} diff --git a/tools/AS1024.Wireguard.Utils.Cli/AS1024.Wireguard.Utils.Cli.csproj b/tools/AS1024.Wireguard.Utils.Cli/AS1024.Wireguard.Utils.Cli.csproj new file mode 100644 index 0000000..5198f31 --- /dev/null +++ b/tools/AS1024.Wireguard.Utils.Cli/AS1024.Wireguard.Utils.Cli.csproj @@ -0,0 +1,14 @@ + + + + Exe + net10.0 + enable + enable + + + + + + + diff --git a/tools/AS1024.Wireguard.Utils.Cli/Program.cs b/tools/AS1024.Wireguard.Utils.Cli/Program.cs new file mode 100644 index 0000000..9e58ff7 --- /dev/null +++ b/tools/AS1024.Wireguard.Utils.Cli/Program.cs @@ -0,0 +1,47 @@ +using AS1024.Wireguard.Utils; + +const string Help = + """ + wgkey - WireGuard key utility + + wgkey gen + wgkey pub + wgkey decode + """; + +if (args.Length == 0 || args[0] is "-h" or "--help" or "help") +{ + Console.WriteLine(Help); + return 0; +} + +try +{ + // TODO: stdin mode? + switch (args[0]) + { + case "gen": + { + var priv = WireGuardKeyUtils.GeneratePrivateKey(); + var pub = WireGuardKeyUtils.GetPublicKey(priv); + Console.WriteLine($"private={Convert.ToBase64String(priv)}"); + Console.WriteLine($"public={Convert.ToBase64String(pub)}"); + return 0; + } + case "pub" when args.Length == 2: + Console.WriteLine(Convert.ToBase64String( + WireGuardKeyUtils.GetPublicKey(WireGuardKeyUtils.FromBase64(args[1])))); + return 0; + case "decode" when args.Length == 2: + Console.WriteLine(Convert.ToHexString(WireGuardKeyUtils.FromBase64(args[1])).ToLowerInvariant()); + return 0; + default: + Console.Error.WriteLine(Help); + return 1; + } +} +catch (Exception e) when (e is ArgumentException or FormatException) +{ + Console.Error.WriteLine(e.Message); + return 1; +}