Merge pull request #2601 from AiyionPrime/key-translate
gluon-mesh-vpn-wireguard: add fastd key migration
This commit is contained in:
		
						commit
						75c62fd2aa
					
				| @ -191,6 +191,16 @@ negative effects. Only when a previously connected node reboots the effect | ||||
| comes into play, as the gateway still knows about the old timestamp of the gluon | ||||
| node. | ||||
| 
 | ||||
| gluon-mesh-vpn-key-translate | ||||
| """""""""""""""""""""""""""" | ||||
| 
 | ||||
| Many communities already possess a collection of active fastd-keys when they | ||||
| plan migrating their community to WireGuard. | ||||
| These public keys known on the server-side can be derived into their WireGuard | ||||
| equivalent using `gluon-mesh-vpn-key-translate <https://github.com/AiyionPrime/gluon-mesh-vpn-key-translate>`__. | ||||
| The routers do the necessary reencoding of the private key seamlessly | ||||
| when updating firmware from fastd to the WireGuard variant. | ||||
| 
 | ||||
| Gateway / Supernode Configuration | ||||
| """"""""""""""""""""""""""""""""" | ||||
| 
 | ||||
|  | ||||
| @ -6,7 +6,13 @@ include ../gluon.mk | ||||
| 
 | ||||
| define Package/gluon-mesh-vpn-wireguard | ||||
|   TITLE:=Support for connecting meshes via wireguard | ||||
|   DEPENDS:=+gluon-core +libgluonutil +gluon-mesh-vpn-core +wireguard-tools +wgpeerselector +libubus | ||||
|   DEPENDS:=+gluon-core +libgluonutil +gluon-mesh-vpn-core +wireguard-tools +wgpeerselector +libubox +libubus | ||||
| endef | ||||
| 
 | ||||
| define Package/gluon-mesh-vpn-wireguard/install | ||||
| 	$(Gluon/Build/Install) | ||||
| 	$(INSTALL_DIR) $(1)/usr/sbin | ||||
| 	$(INSTALL_BIN) $(PKG_BUILD_DIR)/gluon-hex-to-b64 $(1)/usr/sbin/ | ||||
| endef | ||||
| 
 | ||||
| $(eval $(call BuildPackageGluon,gluon-mesh-vpn-wireguard)) | ||||
|  | ||||
| @ -1,18 +1,63 @@ | ||||
| #!/usr/bin/lua | ||||
| 
 | ||||
| local uci = require('simple-uci').cursor() | ||||
| local unistd = require 'posix.unistd' | ||||
| local util = require('gluon.util') | ||||
| local site = require 'gluon.site' | ||||
| local sp = util.subprocess | ||||
| local wait = require 'posix.sys.wait' | ||||
| 
 | ||||
| local private_key = uci:get("network_gluon-old", 'wg_mesh', "private_key") | ||||
| local wg_private_key = uci:get("network_gluon-old", 'wg_mesh', "private_key") | ||||
| 
 | ||||
| if not private_key or not private_key:match("^" .. ("[%a%d+/]"):rep(42) .. "[AEIMQUYcgkosw480]=$") then | ||||
| 	private_key = "generate" | ||||
| local function valid_fastd_key(fastd_key) | ||||
| 	return fastd_key and fastd_key:match(('%x'):rep(64)) | ||||
| end | ||||
| 
 | ||||
| local function valid_wireguard_key(wireguard_key) | ||||
| 	return wireguard_key and wireguard_key:match("^" .. ("[%a%d+/]"):rep(42) .. "[AEIMQUYcgkosw480]=$") | ||||
| end | ||||
| 
 | ||||
| local function migrate_from_fastd_secret(fastd_secret) | ||||
| 	local options = { | ||||
| 		stdin = sp.PIPE, | ||||
| 		stdout = sp.PIPE, | ||||
| 	} | ||||
| 	local pid, pipe = sp.popen('gluon-hex-to-b64', {}, options) | ||||
| 
 | ||||
| 	if not pid then | ||||
| 		return | ||||
| 	end | ||||
| 
 | ||||
| 	local inw = pipe.stdin | ||||
| 	local out = pipe.stdout | ||||
| 
 | ||||
| 	unistd.write(inw, string.format('%s\n', fastd_secret)) | ||||
| 	unistd.close(inw) | ||||
| 
 | ||||
| 	local wpid, status, code = wait.wait(pid) | ||||
| 	if wpid and status == 'exited' and code == 0 then | ||||
| 		local result = unistd.read(out, 44) | ||||
| 		unistd.close(out) | ||||
| 		return result | ||||
| 	end | ||||
| end | ||||
| 
 | ||||
| if not valid_wireguard_key(wg_private_key) then | ||||
| 	local fastd_secret = uci:get('fastd', 'mesh_vpn', 'secret') | ||||
| 	if valid_fastd_key(fastd_secret) then | ||||
| 		wg_private_key = migrate_from_fastd_secret(fastd_secret) | ||||
| 	end | ||||
| end | ||||
| 
 | ||||
| if not valid_wireguard_key(wg_private_key) then | ||||
| 	wg_private_key = "generate" | ||||
| end | ||||
| 
 | ||||
| 
 | ||||
| uci:section('network', 'interface', 'wg_mesh', { | ||||
| 	proto = 'wireguard', | ||||
| 	fwmark = 1, | ||||
| 	private_key = private_key, | ||||
| 	private_key = wg_private_key, | ||||
| }) | ||||
| 
 | ||||
| uci:section('network', 'interface', 'mesh_wg_mesh', { | ||||
|  | ||||
| @ -1,6 +1,9 @@ | ||||
| all: respondd.so | ||||
| all: respondd.so gluon-hex-to-b64 | ||||
| 
 | ||||
| CFLAGS += -Wall -Werror-implicit-function-declaration | ||||
| 
 | ||||
| gluon-hex-to-b64: gluon-hex-to-b64.c | ||||
| 	$(CC) $(CPPFLAGS) $(CFLAGS) $(LDFLAGS) -Wall -o $@ $^ $(LDLIBS) -lubox | ||||
| 
 | ||||
| respondd.so: respondd.c | ||||
| 	$(CC) $(CPPFLAGS) $(CFLAGS) $(LDFLAGS) -shared -fPIC -D_GNU_SOURCE -o $@ $^ $(LDLIBS) -lgluonutil -lubus | ||||
|  | ||||
							
								
								
									
										101
									
								
								package/gluon-mesh-vpn-wireguard/src/gluon-hex-to-b64.c
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										101
									
								
								package/gluon-mesh-vpn-wireguard/src/gluon-hex-to-b64.c
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,101 @@ | ||||
| // SPDX-FileCopyrightText: 2022 Jan-Niklas Burfeind <gluon@aiyionpri.me>
 | ||||
| // SPDX-License-Identifier: BSD-2-Clause
 | ||||
| // SPDX-FileContributor: read_hex() by Matthias Schiffer <mschiffer@universe-factory.net>
 | ||||
| 
 | ||||
| #include <libubox/utils.h> | ||||
| #include <stdbool.h> | ||||
| #include <stdint.h> | ||||
| #include <stdio.h> | ||||
| #include <stdlib.h> | ||||
| #include <string.h> | ||||
| 
 | ||||
| /**
 | ||||
|  * how many blocks should be encoded at once - can be configured | ||||
|  */ | ||||
| #define BLOCK_AMOUNT 32 | ||||
| 
 | ||||
| /**
 | ||||
|  * smallest possible block size to encode in b64 without further contex | ||||
|  * is three bytes - do not change | ||||
|  */ | ||||
| #define CHUNK_SIZE (3*BLOCK_AMOUNT) | ||||
| 
 | ||||
| /** print usage info and exit as failed */ | ||||
| static void usage(void) { | ||||
| 	fprintf(stderr, "Usage: gluon-hex-to-b64\n"); | ||||
| 	exit(1); | ||||
| } | ||||
| 
 | ||||
| /**
 | ||||
|  * read a string of hexadecimal characters and return them as bytes | ||||
|  * return false in case any non-hexadecimal characters are provided | ||||
|  * return true on success | ||||
|  */ | ||||
| static bool read_hex(uint8_t key[CHUNK_SIZE], const char *hexstr) { | ||||
| 	if (strspn(hexstr, "0123456789abcdefABCDEF") != strlen(hexstr)) | ||||
| 		return false; | ||||
| 
 | ||||
| 	size_t i; | ||||
| 	for (i = 0; i < CHUNK_SIZE; i++) | ||||
| 		sscanf(&hexstr[2 * i], "%02hhx", &key[i]); | ||||
| 
 | ||||
| 	return true; | ||||
| } | ||||
| 
 | ||||
| int main(int argc, char *argv[]) { | ||||
| 	if (argc != 1) | ||||
| 		usage(); | ||||
| 
 | ||||
| 	unsigned char hex_input[CHUNK_SIZE * 2 + 1]; | ||||
| 	uint8_t as_bytes[CHUNK_SIZE]; | ||||
| 	int byte_count; | ||||
| 	int b64_buflen = B64_ENCODE_LEN(CHUNK_SIZE); | ||||
| 	int b64_return; | ||||
| 	size_t ret; | ||||
| 
 | ||||
| 	char str[b64_buflen]; | ||||
| 
 | ||||
| 	do { | ||||
| 		ret = fread(hex_input, 1, sizeof(hex_input) - 1, stdin); | ||||
| 		hex_input[ret] = '\0'; | ||||
| 
 | ||||
| 		/* in case fread did not fill six characters */ | ||||
| 		if (ret != sizeof(hex_input)-1) { | ||||
| 			/* drop newline by replacing it with a null character */ | ||||
| 			hex_input[strcspn(hex_input, "\n")] = 0; | ||||
| 
 | ||||
| 			/*
 | ||||
| 			 * count length of resulting string and make sure it's even, | ||||
| 			 * as bytes are represented using pairs of hex characters | ||||
| 			 */ | ||||
| 			ret = strlen(hex_input); | ||||
| 			if (ret % 2 == 1) { | ||||
| 				fprintf(stderr, "Error: Incomplete hex representation of a byte.\n"); | ||||
| 				exit(EXIT_FAILURE); | ||||
| 			} | ||||
| 		} | ||||
| 
 | ||||
| 		byte_count = ret / 2; | ||||
| 		b64_buflen = B64_ENCODE_LEN(byte_count); | ||||
| 
 | ||||
| 		/* in case read_hex fails due to invalid characters */ | ||||
| 		if (!read_hex(as_bytes, hex_input)) { | ||||
| 			fprintf(stderr, "Error: Invalid hexadecimal input.\n"); | ||||
| 			exit(EXIT_FAILURE); | ||||
| 		} | ||||
| 
 | ||||
| 		b64_return = b64_encode(as_bytes, byte_count, str, b64_buflen); | ||||
| 
 | ||||
| 		/* trailing '\0' is not counted by b64_encode(), so we subtract one character */ | ||||
| 		if (b64_buflen - 1 != b64_return) { | ||||
| 			fprintf(stderr, "Error: Encoding bytes as b64 failed.\n"); | ||||
| 			exit(EXIT_FAILURE); | ||||
| 		} | ||||
| 
 | ||||
| 		printf("%s", str); | ||||
| 	/* repeat until a non full block is read */ | ||||
| 	} while (ret == sizeof(hex_input)-1); | ||||
| 	printf("\n"); | ||||
| 
 | ||||
| 	exit(EXIT_SUCCESS); | ||||
| } | ||||
		Loading…
	
		Reference in New Issue
	
	Block a user