wg: side channel resistant base64

Signed-off-by: Jason A. Donenfeld <Jason@zx2c4.com>
This commit is contained in:
Jason A. Donenfeld 2017-04-16 01:20:43 +02:00
parent d42dd68add
commit 755217bd85
9 changed files with 89 additions and 262 deletions

View file

@ -36,7 +36,6 @@ CFLAGS += -std=gnu11
CFLAGS += -Wall -Wextra CFLAGS += -Wall -Wextra
CFLAGS += -MMD -MP CFLAGS += -MMD -MP
CFLAGS += -DRUNSTATEDIR="\"$(RUNSTATEDIR)\"" CFLAGS += -DRUNSTATEDIR="\"$(RUNSTATEDIR)\""
LDLIBS += -lresolv
ifeq ($(shell uname -s),Linux) ifeq ($(shell uname -s),Linux)
LIBMNL_CFLAGS := $(shell $(PKG_CONFIG) --cflags libmnl 2>/dev/null) LIBMNL_CFLAGS := $(shell $(PKG_CONFIG) --cflags libmnl 2>/dev/null)
LIBMNL_LDLIBS := $(shell $(PKG_CONFIG) --libs libmnl 2>/dev/null || echo -lmnl) LIBMNL_LDLIBS := $(shell $(PKG_CONFIG) --libs libmnl 2>/dev/null || echo -lmnl)

View file

@ -1,220 +1,66 @@
/* /* Copyright (C) 2015-2017 Jason A. Donenfeld <Jason@zx2c4.com>. All Rights Reserved.
* Copyright (c) 1996, 1998 by Internet Software Consortium.
* *
* Permission to use, copy, modify, and distribute this software for any * This is a specialized constant-time base64 implementation that resists side-channel attacks.
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND INTERNET SOFTWARE CONSORTIUM DISCLAIMS
* ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES
* OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL INTERNET SOFTWARE
* CONSORTIUM BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL
* DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR
* PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS
* ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS
* SOFTWARE.
*
* Portions Copyright (c) 1995 by International Business Machines, Inc.
*
* International Business Machines, Inc. (hereinafter called IBM) grants
* permission under its copyrights to use, copy, modify, and distribute this
* Software with or without fee, provided that the above copyright notice and
* all paragraphs of this notice appear in all copies, and that the name of IBM
* not be used in connection with the marketing of any product incorporating
* the Software or modifications thereof, without specific, written prior
* permission.
*
* To the extent it has a right to do so, IBM grants an immunity from suit
* under its patents, if any, for the use, sale or manufacture of products to
* the extent that such products are used for performing Domain Name System
* dynamic updates in TCP/IP networks by means of the Software. No immunity is
* granted for any product per se or for any other function of any product.
*
* THE SOFTWARE IS PROVIDED "AS IS", AND IBM DISCLAIMS ALL WARRANTIES,
* INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
* PARTICULAR PURPOSE. IN NO EVENT SHALL IBM BE LIABLE FOR ANY SPECIAL,
* DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER ARISING
* OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE, EVEN
* IF IBM IS APPRISED OF THE POSSIBILITY OF SUCH DAMAGES.
*/ */
#include <string.h>
#include "base64.h" #include "base64.h"
#include <sys/types.h>
#include <assert.h>
#include <stdlib.h>
#include <ctype.h>
#if defined(NEED_B64_NTOP) || defined(NEED_B64_PTON) static inline void encode(char dest[4], const uint8_t src[3])
static const char base64[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
static const char pad64 = '=';
#endif
#ifdef NEED_B64_NTOP
int b64_ntop(unsigned char const *src, size_t srclength, char *target, size_t targsize)
{ {
size_t datalength = 0; const uint8_t input[] = { (src[0] >> 2) & 63, ((src[0] << 4) | (src[1] >> 4)) & 63, ((src[1] << 2) | (src[2] >> 6)) & 63, src[2] & 63 };
uint8_t input[3]; for (unsigned int i = 0; i < 4; ++i)
uint8_t output[4]; dest[i] = input[i] + 'A'
size_t i; + (((25 - input[i]) >> 8) & 6)
- (((51 - input[i]) >> 8) & 75)
- (((61 - input[i]) >> 8) & 15)
+ (((62 - input[i]) >> 8) & 3);
while (2 < srclength) {
input[0] = *src++;
input[1] = *src++;
input[2] = *src++;
srclength -= 3;
output[0] = input[0] >> 2;
output[1] = ((input[0] & 0x03) << 4) + (input[1] >> 4);
output[2] = ((input[1] & 0x0f) << 2) + (input[2] >> 6);
output[3] = input[2] & 0x3f;
assert(output[0] < 64);
assert(output[1] < 64);
assert(output[2] < 64);
assert(output[3] < 64);
if (datalength + 4 > targsize)
return -1;
target[datalength++] = base64[output[0]];
target[datalength++] = base64[output[1]];
target[datalength++] = base64[output[2]];
target[datalength++] = base64[output[3]];
} }
if (0 != srclength) {
input[0] = input[1] = input[2] = '\0';
for (i = 0; i < srclength; i++)
input[i] = *src++;
output[0] = input[0] >> 2;
output[1] = ((input[0] & 0x03) << 4) + (input[1] >> 4);
output[2] = ((input[1] & 0x0f) << 2) + (input[2] >> 6);
assert(output[0] < 64);
assert(output[1] < 64);
assert(output[2] < 64);
if (datalength + 4 > targsize) void key_to_base64(char base64[static WG_KEY_LEN_BASE64], const uint8_t key[static WG_KEY_LEN])
return -1;
target[datalength++] = base64[output[0]];
target[datalength++] = base64[output[1]];
if (srclength == 1)
target[datalength++] = pad64;
else
target[datalength++] = base64[output[2]];
target[datalength++] = pad64;
}
if (datalength >= targsize)
return (-1);
target[datalength] = '\0';
return datalength;
}
#endif
#ifdef NEED_B64_PTON
int b64_pton(char const *src, uint8_t *target, size_t targsize)
{ {
static int b64rmap_initialized = 0; unsigned int i;
static uint8_t b64rmap[256]; for (i = 0; i < WG_KEY_LEN / 3; ++i)
static const uint8_t b64rmap_special = 0xf0; encode(&base64[i * 4], &key[i * 3]);
static const uint8_t b64rmap_end = 0xfd; encode(&base64[i * 4], (const uint8_t[]){ key[i * 3 + 0], key[i * 3 + 1], 0 });
static const uint8_t b64rmap_space = 0xfe; base64[WG_KEY_LEN_BASE64 - 2] = '=';
static const uint8_t b64rmap_invalid = 0xff; base64[WG_KEY_LEN_BASE64 - 1] = '\0';
int tarindex, state, ch;
uint8_t ofs;
if (!b64rmap_initialized) {
int i;
char ch;
b64rmap[0] = b64rmap_end;
for (i = 1; i < 256; ++i) {
ch = (char)i;
if (isspace(ch))
b64rmap[i] = b64rmap_space;
else if (ch == pad64)
b64rmap[i] = b64rmap_end;
else
b64rmap[i] = b64rmap_invalid;
}
for (i = 0; base64[i] != '\0'; ++i)
b64rmap[(uint8_t)base64[i]] = i;
b64rmap_initialized = 1;
} }
state = 0; static inline int decode(const char src[4])
tarindex = 0; {
int val = 0;
for (;;) { for (unsigned int i = 0; i < 4; ++i)
ch = *src++; val |= (-1
ofs = b64rmap[ch]; + ((((('A' - 1) - src[i]) & (src[i] - ('Z' + 1))) >> 8) & (src[i] - 64))
+ ((((('a' - 1) - src[i]) & (src[i] - ('z' + 1))) >> 8) & (src[i] - 70))
if (ofs >= b64rmap_special) { + ((((('0' - 1) - src[i]) & (src[i] - ('9' + 1))) >> 8) & (src[i] + 5))
if (ofs == b64rmap_space) + ((((('+' - 1) - src[i]) & (src[i] - ('+' + 1))) >> 8) & 63)
continue; + ((((('/' - 1) - src[i]) & (src[i] - ('/' + 1))) >> 8) & 64)
if (ofs == b64rmap_end) ) << (18 - 6 * i);
break; return val;
return -1;
} }
switch (state) { bool key_from_base64(uint8_t key[static WG_KEY_LEN], const char *base64)
case 0: {
if ((size_t)tarindex >= targsize) unsigned int i;
return -1; int val;
target[tarindex] = ofs << 2; if (strlen(base64) != WG_KEY_LEN_BASE64 - 1 || base64[WG_KEY_LEN_BASE64 - 2] != '=')
state = 1; return false;
break;
case 1:
if ((size_t)tarindex + 1 >= targsize)
return -1;
target[tarindex] |= ofs >> 4;
target[tarindex+1] = (ofs & 0x0f) << 4 ;
tarindex++;
state = 2;
break;
case 2:
if ((size_t)tarindex + 1 >= targsize)
return -1;
target[tarindex] |= ofs >> 2;
target[tarindex+1] = (ofs & 0x03) << 6;
tarindex++;
state = 3;
break;
case 3:
if ((size_t)tarindex >= targsize)
return -1;
target[tarindex] |= ofs;
tarindex++;
state = 0;
break;
default:
abort();
}
}
if (ch == pad64) { for (i = 0; i < WG_KEY_LEN / 3; ++i) {
ch = *src++; val = decode(&base64[i * 4]);
switch (state) { if (val < 0)
case 0: return false;
case 1: key[i * 3 + 0] = (val >> 16) & 0xff;
return -1; key[i * 3 + 1] = (val >> 8) & 0xff;
key[i * 3 + 2] = val & 0xff;
case 2:
for (; ch; ch = *src++) {
if (b64rmap[ch] != b64rmap_space)
break;
} }
if (ch != pad64) val = decode((const char[]){ base64[i * 4 + 0], base64[i * 4 + 1], base64[i * 4 + 2], 'A' });
return -1; if (val < 0 || val & 0xff)
ch = *src++; return false;
case 3: key[i * 3 + 0] = (val >> 16) & 0xff;
for (; ch; ch = *src++) { key[i * 3 + 1] = (val >> 8) & 0xff;
if (b64rmap[ch] != b64rmap_space) return true;
return -1;
} }
if (target[tarindex] != 0)
return -1;
}
} else {
if (state != 0)
return -1;
}
return tarindex;
}
#endif

View file

@ -3,18 +3,13 @@
#ifndef BASE64_H #ifndef BASE64_H
#define BASE64_H #define BASE64_H
#include <resolv.h> #include <stdbool.h>
#include <stdint.h>
#include "../uapi.h"
#define b64_len(len) ((((len) + 2) / 3) * 4 + 1) #define WG_KEY_LEN_BASE64 ((((WG_KEY_LEN) + 2) / 3) * 4 + 1)
#ifndef b64_ntop void key_to_base64(char base64[static WG_KEY_LEN_BASE64], const uint8_t key[static WG_KEY_LEN]);
int b64_ntop(unsigned char const *, size_t, char *, size_t); bool key_from_base64(uint8_t key[static WG_KEY_LEN], const char *base64);
#define NEED_B64_NTOP
#endif
#ifndef b64_pton
int b64_pton(char const *, unsigned char *, size_t);
#define NEED_B64_PTON
#endif
#endif #endif

View file

@ -118,12 +118,10 @@ static inline bool parse_fwmark(uint32_t *fwmark, unsigned int *flags, const cha
static inline bool parse_key(uint8_t key[static WG_KEY_LEN], const char *value) static inline bool parse_key(uint8_t key[static WG_KEY_LEN], const char *value)
{ {
uint8_t tmp[WG_KEY_LEN + 1]; if (!key_from_base64(key, value)) {
if (strlen(value) != b64_len(WG_KEY_LEN) - 1 || b64_pton(value, tmp, WG_KEY_LEN + 1) != WG_KEY_LEN) {
fprintf(stderr, "Key is not the correct length or format: `%s`\n", value); fprintf(stderr, "Key is not the correct length or format: `%s`\n", value);
return false; return false;
} }
memcpy(key, tmp, WG_KEY_LEN);
return true; return true;
} }

View file

@ -34,8 +34,8 @@ static inline ssize_t get_random_bytes(uint8_t *out, size_t len)
int genkey_main(int argc, char *argv[]) int genkey_main(int argc, char *argv[])
{ {
unsigned char private_key[CURVE25519_POINT_SIZE]; uint8_t key[WG_KEY_LEN];
char private_key_base64[b64_len(CURVE25519_POINT_SIZE)]; char base64[WG_KEY_LEN_BASE64];
struct stat stat; struct stat stat;
if (argc != 1) { if (argc != 1) {
@ -46,19 +46,14 @@ int genkey_main(int argc, char *argv[])
if (!fstat(STDOUT_FILENO, &stat) && S_ISREG(stat.st_mode) && stat.st_mode & S_IRWXO) if (!fstat(STDOUT_FILENO, &stat) && S_ISREG(stat.st_mode) && stat.st_mode & S_IRWXO)
fputs("Warning: writing to world accessible file.\nConsider setting the umask to 077 and trying again.\n", stderr); fputs("Warning: writing to world accessible file.\nConsider setting the umask to 077 and trying again.\n", stderr);
if (get_random_bytes(private_key, CURVE25519_POINT_SIZE) != CURVE25519_POINT_SIZE) { if (get_random_bytes(key, WG_KEY_LEN) != WG_KEY_LEN) {
perror("getrandom"); perror("getrandom");
return 1; return 1;
} }
if (argc && !strcmp(argv[0], "genkey")) if (argc && !strcmp(argv[0], "genkey"))
curve25519_normalize_secret(private_key); curve25519_normalize_secret(key);
if (b64_ntop(private_key, sizeof(private_key), private_key_base64, sizeof(private_key_base64)) != sizeof(private_key_base64) - 1) { key_to_base64(base64, key);
fprintf(stderr, "%s: Could not convert key to base64\n", PROG_NAME); puts(base64);
return 1;
}
puts(private_key_base64);
return 0; return 0;
} }

View file

@ -177,7 +177,7 @@ static int userspace_set_device(struct wgdevice *dev)
if (fd < 0) if (fd < 0)
return fd; return fd;
for_each_wgpeer(dev, peer, len); for_each_wgpeer(dev, peer, len);
len = (unsigned char *)peer - (unsigned char *)dev; len = (uint8_t *)peer - (uint8_t *)dev;
ret = -EBADMSG; ret = -EBADMSG;
if (!len) if (!len)
goto out; goto out;

View file

@ -1,7 +1,6 @@
/* Copyright (C) 2015-2017 Jason A. Donenfeld <Jason@zx2c4.com>. All Rights Reserved. */ /* Copyright (C) 2015-2017 Jason A. Donenfeld <Jason@zx2c4.com>. All Rights Reserved. */
#include <errno.h> #include <errno.h>
#include <resolv.h>
#include <stdio.h> #include <stdio.h>
#include <ctype.h> #include <ctype.h>
@ -11,8 +10,8 @@
int pubkey_main(int argc, char *argv[]) int pubkey_main(int argc, char *argv[])
{ {
unsigned char private_key[CURVE25519_POINT_SIZE + 1] = { 0 }, public_key[CURVE25519_POINT_SIZE] = { 0 }; uint8_t key[WG_KEY_LEN];
char private_key_base64[b64_len(CURVE25519_POINT_SIZE)] = { 0 }, public_key_base64[b64_len(CURVE25519_POINT_SIZE)] = { 0 }; char base64[WG_KEY_LEN_BASE64];
int trailing_char; int trailing_char;
if (argc != 1) { if (argc != 1) {
@ -20,11 +19,12 @@ int pubkey_main(int argc, char *argv[])
return 1; return 1;
} }
if (fread(private_key_base64, 1, sizeof(private_key_base64) - 1, stdin) != sizeof(private_key_base64) - 1) { if (fread(base64, 1, sizeof(base64) - 1, stdin) != sizeof(base64) - 1) {
errno = EINVAL; errno = EINVAL;
fprintf(stderr, "%s: Key is not the correct length or format\n", PROG_NAME); fprintf(stderr, "%s: Key is not the correct length or format\n", PROG_NAME);
return 1; return 1;
} }
base64[WG_KEY_LEN_BASE64 - 1] = '\0';
for (;;) { for (;;) {
trailing_char = getc(stdin); trailing_char = getc(stdin);
@ -36,15 +36,12 @@ int pubkey_main(int argc, char *argv[])
return 1; return 1;
} }
if (b64_pton(private_key_base64, private_key, sizeof(private_key)) != sizeof(private_key) - 1) { if (!key_from_base64(key, base64)) {
fprintf(stderr, "%s: Key is not the correct length or format\n", PROG_NAME); fprintf(stderr, "%s: Key is not the correct length or format\n", PROG_NAME);
return 1; return 1;
} }
curve25519_generate_public(public_key, private_key); curve25519_generate_public(key, key);
if (b64_ntop(public_key, sizeof(public_key), public_key_base64, sizeof(public_key_base64)) != sizeof(public_key_base64) - 1) { key_to_base64(base64, key);
fprintf(stderr, "%s: Could not convert key to base64\n", PROG_NAME); puts(base64);
return 1;
}
puts(public_key_base64);
return 0; return 0;
} }

View file

@ -4,7 +4,6 @@
#include <inttypes.h> #include <inttypes.h>
#include <netinet/in.h> #include <netinet/in.h>
#include <net/if.h> #include <net/if.h>
#include <resolv.h>
#include <stdbool.h> #include <stdbool.h>
#include <stddef.h> #include <stddef.h>
#include <stdint.h> #include <stdint.h>
@ -78,17 +77,16 @@ static void sort_peers(struct wgdevice *device)
static const uint8_t zero[WG_KEY_LEN] = { 0 }; static const uint8_t zero[WG_KEY_LEN] = { 0 };
static char *key(const unsigned char key[static WG_KEY_LEN]) static char *key(const uint8_t key[static WG_KEY_LEN])
{ {
static char b64[b64_len(WG_KEY_LEN)]; static char base64[WG_KEY_LEN_BASE64];
if (!memcmp(key, zero, WG_KEY_LEN)) if (!memcmp(key, zero, WG_KEY_LEN))
return "(none)"; return "(none)";
memset(b64, 0, b64_len(WG_KEY_LEN)); key_to_base64(base64, key);
b64_ntop(key, WG_KEY_LEN, b64, b64_len(WG_KEY_LEN)); return base64;
return b64;
} }
static char *masked_key(const unsigned char masked_key[static WG_KEY_LEN]) static char *masked_key(const uint8_t masked_key[static WG_KEY_LEN])
{ {
const char *var = getenv("WG_HIDE_KEYS"); const char *var = getenv("WG_HIDE_KEYS");
if (var && !strcmp(var, "never")) if (var && !strcmp(var, "never"))

View file

@ -3,7 +3,6 @@
#include <arpa/inet.h> #include <arpa/inet.h>
#include <netinet/in.h> #include <netinet/in.h>
#include <net/if.h> #include <net/if.h>
#include <resolv.h>
#include <stddef.h> #include <stddef.h>
#include <stdio.h> #include <stdio.h>
#include <string.h> #include <string.h>
@ -18,7 +17,7 @@
int showconf_main(int argc, char *argv[]) int showconf_main(int argc, char *argv[])
{ {
static const uint8_t zero[WG_KEY_LEN] = { 0 }; static const uint8_t zero[WG_KEY_LEN] = { 0 };
char b64[b64_len(WG_KEY_LEN)] = { 0 }; char base64[WG_KEY_LEN_BASE64];
char ip[INET6_ADDRSTRLEN]; char ip[INET6_ADDRSTRLEN];
struct wgdevice *device = NULL; struct wgdevice *device = NULL;
struct wgpeer *peer; struct wgpeer *peer;
@ -48,17 +47,17 @@ int showconf_main(int argc, char *argv[])
if (device->fwmark) if (device->fwmark)
printf("FwMark = 0x%x\n", device->fwmark); printf("FwMark = 0x%x\n", device->fwmark);
if (memcmp(device->private_key, zero, WG_KEY_LEN)) { if (memcmp(device->private_key, zero, WG_KEY_LEN)) {
b64_ntop(device->private_key, WG_KEY_LEN, b64, b64_len(WG_KEY_LEN)); key_to_base64(base64, device->private_key);
printf("PrivateKey = %s\n", b64); printf("PrivateKey = %s\n", base64);
} }
if (memcmp(device->preshared_key, zero, WG_KEY_LEN)) { if (memcmp(device->preshared_key, zero, WG_KEY_LEN)) {
b64_ntop(device->preshared_key, WG_KEY_LEN, b64, b64_len(WG_KEY_LEN)); key_to_base64(base64, device->preshared_key);
printf("PresharedKey = %s\n", b64); printf("PresharedKey = %s\n", base64);
} }
printf("\n"); printf("\n");
for_each_wgpeer(device, peer, i) { for_each_wgpeer(device, peer, i) {
b64_ntop(peer->public_key, WG_KEY_LEN, b64, b64_len(WG_KEY_LEN)); key_to_base64(base64, peer->public_key);
printf("[Peer]\nPublicKey = %s\n", b64); printf("[Peer]\nPublicKey = %s\n", base64);
if (peer->num_ipmasks) if (peer->num_ipmasks)
printf("AllowedIPs = "); printf("AllowedIPs = ");
for_each_wgipmask(peer, ipmask, j) { for_each_wgipmask(peer, ipmask, j) {