diff options
author | Artem Glazychev <artem.glazychev@xored.com> | 2020-08-31 17:12:30 +0700 |
---|---|---|
committer | Damjan Marion <dmarion@me.com> | 2020-09-09 11:57:48 +0000 |
commit | edca1325cf296bd0f5ff422fc12de2ce7a7bad88 (patch) | |
tree | fb12d12bd4193c5b2c7559d98aba9dc5d2f14e85 | |
parent | ef80ad6bff03e3cc35950de0e15e4821ef3f7c04 (diff) |
wireguard: initial implementation of wireguard protocol
Type: feature
The main information about plugin you can see in README.md
vpp# wireguard ?
wireguard create wireguard create listen-port <port> private-key <key> src <IP> [generate-key]
wireguard delete wireguard delete <interface>
wireguard peer add wireguard peer add <wg_int> public-key <pub_key_other>endpoint <ip4_dst> allowed-ip <prefix>dst-port [port_dst] persistent-keepalive [keepalive_interval]
wireguard peer remove wireguard peer remove <index>
Change-Id: I85eb0bfc033ccfb2045696398d8a108b1c64b8d9
Signed-off-by: Artem Glazychev <artem.glazychev@xored.com>
Signed-off-by: Damjan Marion <damarion@cisco.com>
Signed-off-by: Jim Thompson <jim@netgate.com>
Signed-off-by: Neale Ranns <nranns@cisco.com>
Signed-off-by: Damjan Marion <damarion@cisco.com>
34 files changed, 6226 insertions, 1 deletions
diff --git a/MAINTAINERS b/MAINTAINERS index 03cd4791b34..e929020fe78 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -688,6 +688,11 @@ M: Nathan Skrzypczak <nathan.skrzypczak@gmail.com> M: Neale Ranns <nranns@cisco.com> F: src/plugins/cnat +Plugin - Wireguard +I: wireguard +M: Artem Glazychev <artem.glazychev@xored.com> +F: src/plugins/wireguard + VPP Config Tooling I: vpp_config M: John DeNisco <jdenisco@cisco.com> diff --git a/src/plugins/wireguard/CMakeLists.txt b/src/plugins/wireguard/CMakeLists.txt new file mode 100755 index 00000000000..db5bb2d8910 --- /dev/null +++ b/src/plugins/wireguard/CMakeLists.txt @@ -0,0 +1,54 @@ + +# Copyright (c) 2020 Doc.ai and/or its affiliates. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at: +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +if (OPENSSL_VERSION VERSION_LESS 1.1.0) + return() +endif() + +list(APPEND WG_BLAKE_SOURCES + blake/blake2s.h + blake/blake2s.c +) + +add_vpp_plugin(wireguard + SOURCES + ${WG_BLAKE_SOURCES} + wireguard.c + wireguard.h + wireguard_if.c + wireguard_if.h + wireguard_input.c + wireguard_output_tun.c + wireguard_key.c + wireguard_key.h + wireguard_cli.c + wireguard_messages.h + wireguard_noise.c + wireguard_noise.h + wireguard_send.c + wireguard_send.h + wireguard_cookie.c + wireguard_cookie.h + wireguard_peer.c + wireguard_peer.h + wireguard_timer.c + wireguard_timer.h + wireguard_index_table.c + wireguard_index_table.h + wireguard_api.c + + API_FILES + wireguard.api + +) diff --git a/src/plugins/wireguard/FEATURE.yaml b/src/plugins/wireguard/FEATURE.yaml new file mode 100644 index 00000000000..cf8b6d7f3c4 --- /dev/null +++ b/src/plugins/wireguard/FEATURE.yaml @@ -0,0 +1,12 @@ +--- +name: Wireguard protocol +maintainer: Artem Glazychev <artem.glazychev@xored.com> +features: + - "based on wireguard-openbsd implementation: https://git.zx2c4.com/wireguard-openbsd" + - creating secure VPN-tunnel +description: "Wireguard protocol implementation" +state: development +properties: [API, CLI] +missing: + - IPv6 support + - DoS protection as in the original protocol diff --git a/src/plugins/wireguard/README.md b/src/plugins/wireguard/README.md new file mode 100755 index 00000000000..a11356cfde2 --- /dev/null +++ b/src/plugins/wireguard/README.md @@ -0,0 +1,74 @@ +# Wireguard vpp-plugin + +## Overview +This plugin is an implementation of [wireguard protocol](https://www.wireguard.com/) for VPP. It allows one to create secure VPN tunnels. +This implementation is based on [wireguard-openbsd](https://git.zx2c4.com/wireguard-openbsd/), using the implementaiton of *ipip-tunnel*. + +## Crypto + +The crypto protocols: + +- blake2s [[Source]](https://github.com/BLAKE2/BLAKE2) + +OpenSSL: + +- curve25519 +- chachapoly1305 + +## Plugin usage example +Usage is very similar to other wireguard implementations. + +### Create connection +Create keys: + +``` +> vpp# wg genkey +> *my_private_key* +> vpp# wg pubkey <my_private_key> +> *my_pub_key* +``` + +Create tunnel: +``` +> vpp# create ipip tunnel src <ip4_src> dst <ip4_dst> +> *tun_name* +> vpp# set int state <tun_name> up +> vpp# set int ip address <tun_name> <tun_ip4> +``` + +After this we can create wg-device. The UDP port is opened automatically. +``` +> vpp# wg set device private-key <my_private_key> src-port <my_port> +``` + +Now, we can add a peer configuration: +``` +> vpp# wg set peer public-key <peer_pub_key> endpoint <peer_ip4> allowed-ip <peer_tun_ip4> dst-port <peer_port> tunnel <tun_name> persistent-keepalive <keepalive_interval> +``` +If you need to add more peers, don't forget to first create another ipip-tunnel. +Ping. +``` +> vpp# ping <peer_tun_ip4> +``` +### Show config +To show device and all peer configurations: +``` +> vpp# show wg +``` + +### Remove peer +Peer can be removed by its public-key. +``` +> vpp# wg remove peer <peer_pub_key> +``` +This removes the associated ipip tunnel as well + +### Clear all connections +``` +> vpp# wg remove device +``` + +## main next steps for improving this implementation +1. Use all benefits of VPP-engine. +2. Add IP6 support (currently only supports IPv4)) +3. Add DoS protection as in original protocol (using cookie) diff --git a/src/plugins/wireguard/blake/blake2-impl.h b/src/plugins/wireguard/blake/blake2-impl.h new file mode 100755 index 00000000000..ad60b4a5775 --- /dev/null +++ b/src/plugins/wireguard/blake/blake2-impl.h @@ -0,0 +1,187 @@ +/* + * Copyright (c) 2020 Doc.ai and/or its affiliates. + * Copyright (c) 2012 Samuel Neves <sneves@dei.uc.pt>. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* + More information about the BLAKE2 hash function can be found at + https://blake2.net. +*/ +#ifndef __included_crypto_blake2_impl_h__ +#define __included_crypto_blake2_impl_h__ + +#include <stdint.h> +#include <string.h> +#include <vppinfra/byte_order.h> + +#if defined(CLIB_ARCH_IS_LITTLE_ENDIAN) +#define NATIVE_LITTLE_ENDIAN +#endif + +#if !defined(__cplusplus) && (!defined(__STDC_VERSION__) || __STDC_VERSION__ < 199901L) +#if defined(_MSC_VER) +#define BLAKE2_INLINE __inline +#elif defined(__GNUC__) +#define BLAKE2_INLINE __inline__ +#else +#define BLAKE2_INLINE +#endif +#else +#define BLAKE2_INLINE inline +#endif + +static BLAKE2_INLINE uint32_t +load32 (const void *src) +{ +#if defined(NATIVE_LITTLE_ENDIAN) + uint32_t w; + memcpy (&w, src, sizeof w); + return w; +#else + const uint8_t *p = (const uint8_t *) src; + return ((uint32_t) (p[0]) << 0) | + ((uint32_t) (p[1]) << 8) | + ((uint32_t) (p[2]) << 16) | ((uint32_t) (p[3]) << 24); +#endif +} + +static BLAKE2_INLINE uint64_t +load64 (const void *src) +{ +#if defined(NATIVE_LITTLE_ENDIAN) + uint64_t w; + memcpy (&w, src, sizeof w); + return w; +#else + const uint8_t *p = (const uint8_t *) src; + return ((uint64_t) (p[0]) << 0) | + ((uint64_t) (p[1]) << 8) | + ((uint64_t) (p[2]) << 16) | + ((uint64_t) (p[3]) << 24) | + ((uint64_t) (p[4]) << 32) | + ((uint64_t) (p[5]) << 40) | + ((uint64_t) (p[6]) << 48) | ((uint64_t) (p[7]) << 56); +#endif +} + +static BLAKE2_INLINE uint16_t +load16 (const void *src) +{ +#if defined(NATIVE_LITTLE_ENDIAN) + uint16_t w; + memcpy (&w, src, sizeof w); + return w; +#else + const uint8_t *p = (const uint8_t *) src; + return (uint16_t) (((uint32_t) (p[0]) << 0) | ((uint32_t) (p[1]) << 8)); +#endif +} + +static BLAKE2_INLINE void +store16 (void *dst, uint16_t w) +{ +#if defined(NATIVE_LITTLE_ENDIAN) + memcpy (dst, &w, sizeof w); +#else + uint8_t *p = (uint8_t *) dst; + *p++ = (uint8_t) w; + w >>= 8; + *p++ = (uint8_t) w; +#endif +} + +static BLAKE2_INLINE void +store32 (void *dst, uint32_t w) +{ +#if defined(NATIVE_LITTLE_ENDIAN) + memcpy (dst, &w, sizeof w); +#else + uint8_t *p = (uint8_t *) dst; + p[0] = (uint8_t) (w >> 0); + p[1] = (uint8_t) (w >> 8); + p[2] = (uint8_t) (w >> 16); + p[3] = (uint8_t) (w >> 24); +#endif +} + +static BLAKE2_INLINE void +store64 (void *dst, uint64_t w) +{ +#if defined(NATIVE_LITTLE_ENDIAN) + memcpy (dst, &w, sizeof w); +#else + uint8_t *p = (uint8_t *) dst; + p[0] = (uint8_t) (w >> 0); + p[1] = (uint8_t) (w >> 8); + p[2] = (uint8_t) (w >> 16); + p[3] = (uint8_t) (w >> 24); + p[4] = (uint8_t) (w >> 32); + p[5] = (uint8_t) (w >> 40); + p[6] = (uint8_t) (w >> 48); + p[7] = (uint8_t) (w >> 56); +#endif +} + +static BLAKE2_INLINE uint64_t +load48 (const void *src) +{ + const uint8_t *p = (const uint8_t *) src; + return ((uint64_t) (p[0]) << 0) | + ((uint64_t) (p[1]) << 8) | + ((uint64_t) (p[2]) << 16) | + ((uint64_t) (p[3]) << 24) | + ((uint64_t) (p[4]) << 32) | ((uint64_t) (p[5]) << 40); +} + +static BLAKE2_INLINE void +store48 (void *dst, uint64_t w) +{ + uint8_t *p = (uint8_t *) dst; + p[0] = (uint8_t) (w >> 0); + p[1] = (uint8_t) (w >> 8); + p[2] = (uint8_t) (w >> 16); + p[3] = (uint8_t) (w >> 24); + p[4] = (uint8_t) (w >> 32); + p[5] = (uint8_t) (w >> 40); +} + +static BLAKE2_INLINE uint32_t +rotr32 (const uint32_t w, const unsigned c) +{ + return (w >> c) | (w << (32 - c)); +} + +static BLAKE2_INLINE uint64_t +rotr64 (const uint64_t w, const unsigned c) +{ + return (w >> c) | (w << (64 - c)); +} + +/* prevents compiler optimizing out memset() */ +static BLAKE2_INLINE void +secure_zero_memory (void *v, size_t n) +{ + static void *(*const volatile memset_v) (void *, int, size_t) = &memset; + memset_v (v, 0, n); +} + +#endif //__included_crypto_blake2_impl_h__ + +/* + * fd.io coding-style-patch-verification: ON + * + * Local Variables: + * eval: (c-set-style "gnu") + * End: + */ diff --git a/src/plugins/wireguard/blake/blake2s.c b/src/plugins/wireguard/blake/blake2s.c new file mode 100755 index 00000000000..3ff312a1322 --- /dev/null +++ b/src/plugins/wireguard/blake/blake2s.c @@ -0,0 +1,335 @@ +/* + * Copyright (c) 2020 Doc.ai and/or its affiliates. + * Copyright (c) 2012 Samuel Neves <sneves@dei.uc.pt>. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +/* + More information about the BLAKE2 hash function can be found at + https://blake2.net. +*/ + +#include <stdint.h> +#include <string.h> +#include <stdio.h> + +#include <wireguard/blake/blake2s.h> +#include "blake2-impl.h" + +static const uint32_t blake2s_IV[8] = { + 0x6A09E667UL, 0xBB67AE85UL, 0x3C6EF372UL, 0xA54FF53AUL, + 0x510E527FUL, 0x9B05688CUL, 0x1F83D9ABUL, 0x5BE0CD19UL +}; + +static const uint8_t blake2s_sigma[10][16] = { + {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15}, + {14, 10, 4, 8, 9, 15, 13, 6, 1, 12, 0, 2, 11, 7, 5, 3}, + {11, 8, 12, 0, 5, 2, 15, 13, 10, 14, 3, 6, 7, 1, 9, 4}, + {7, 9, 3, 1, 13, 12, 11, 14, 2, 6, 5, 10, 4, 0, 15, 8}, + {9, 0, 5, 7, 2, 4, 10, 15, 14, 1, 11, 12, 6, 8, 3, 13}, + {2, 12, 6, 10, 0, 11, 8, 3, 4, 13, 7, 5, 15, 14, 1, 9}, + {12, 5, 1, 15, 14, 13, 4, 10, 0, 7, 6, 3, 9, 2, 8, 11}, + {13, 11, 7, 14, 12, 1, 3, 9, 5, 0, 15, 4, 8, 6, 2, 10}, + {6, 15, 14, 9, 11, 3, 0, 8, 12, 2, 13, 7, 1, 4, 10, 5}, + {10, 2, 8, 4, 7, 6, 1, 5, 15, 11, 9, 14, 3, 12, 13, 0}, +}; + +static void +blake2s_set_lastnode (blake2s_state_t * S) +{ + S->f[1] = (uint32_t) - 1; +} + +/* Some helper functions, not necessarily useful */ +static int +blake2s_is_lastblock (const blake2s_state_t * S) +{ + return S->f[0] != 0; +} + +static void +blake2s_set_lastblock (blake2s_state_t * S) +{ + if (S->last_node) + blake2s_set_lastnode (S); + + S->f[0] = (uint32_t) - 1; +} + +static void +blake2s_increment_counter (blake2s_state_t * S, const uint32_t inc) +{ + S->t[0] += inc; + S->t[1] += (S->t[0] < inc); +} + +static void +blake2s_init0 (blake2s_state_t * S) +{ + size_t i; + memset (S, 0, sizeof (blake2s_state_t)); + + for (i = 0; i < 8; ++i) + S->h[i] = blake2s_IV[i]; +} + +/* init2 xors IV with input parameter block */ +int +blake2s_init_param (blake2s_state_t * S, const blake2s_param_t * P) +{ + const unsigned char *p = (const unsigned char *) (P); + size_t i; + + blake2s_init0 (S); + + /* IV XOR ParamBlock */ + for (i = 0; i < 8; ++i) + S->h[i] ^= load32 (&p[i * 4]); + + S->outlen = P->digest_length; + return 0; +} + + +/* Sequential blake2s initialization */ +int +blake2s_init (blake2s_state_t * S, size_t outlen) +{ + blake2s_param_t P[1]; + + /* Move interval verification here? */ + if ((!outlen) || (outlen > BLAKE2S_OUT_BYTES)) + return -1; + + P->digest_length = (uint8_t) outlen; + P->key_length = 0; + P->fanout = 1; + P->depth = 1; + store32 (&P->leaf_length, 0); + store32 (&P->node_offset, 0); + store16 (&P->xof_length, 0); + P->node_depth = 0; + P->inner_length = 0; + /* memset(P->reserved, 0, sizeof(P->reserved) ); */ + memset (P->salt, 0, sizeof (P->salt)); + memset (P->personal, 0, sizeof (P->personal)); + return blake2s_init_param (S, P); +} + +int +blake2s_init_key (blake2s_state_t * S, size_t outlen, const void *key, + size_t keylen) +{ + blake2s_param_t P[1]; + + if ((!outlen) || (outlen > BLAKE2S_OUT_BYTES)) + return -1; + + if (!key || !keylen || keylen > BLAKE2S_KEY_BYTES) + return -1; + + P->digest_length = (uint8_t) outlen; + P->key_length = (uint8_t) keylen; + P->fanout = 1; + P->depth = 1; + store32 (&P->leaf_length, 0); + store32 (&P->node_offset, 0); + store16 (&P->xof_length, 0); + P->node_depth = 0; + P->inner_length = 0; + /* memset(P->reserved, 0, sizeof(P->reserved) ); */ + memset (P->salt, 0, sizeof (P->salt)); + memset (P->personal, 0, sizeof (P->personal)); + + if (blake2s_init_param (S, P) < 0) + return -1; + + { + uint8_t block[BLAKE2S_BLOCK_BYTES]; + memset (block, 0, BLAKE2S_BLOCK_BYTES); + memcpy (block, key, keylen); + blake2s_update (S, block, BLAKE2S_BLOCK_BYTES); + secure_zero_memory (block, BLAKE2S_BLOCK_BYTES); /* Burn the key from stack */ + } + return 0; +} + +#define G(r,i,a,b,c,d) \ + do { \ + a = a + b + m[blake2s_sigma[r][2*i+0]]; \ + d = rotr32(d ^ a, 16); \ + c = c + d; \ + b = rotr32(b ^ c, 12); \ + a = a + b + m[blake2s_sigma[r][2*i+1]]; \ + d = rotr32(d ^ a, 8); \ + c = c + d; \ + b = rotr32(b ^ c, 7); \ + } while(0) + +#define ROUND(r) \ + do { \ + G(r,0,v[ 0],v[ 4],v[ 8],v[12]); \ + G(r,1,v[ 1],v[ 5],v[ 9],v[13]); \ + G(r,2,v[ 2],v[ 6],v[10],v[14]); \ + G(r,3,v[ 3],v[ 7],v[11],v[15]); \ + G(r,4,v[ 0],v[ 5],v[10],v[15]); \ + G(r,5,v[ 1],v[ 6],v[11],v[12]); \ + G(r,6,v[ 2],v[ 7],v[ 8],v[13]); \ + G(r,7,v[ 3],v[ 4],v[ 9],v[14]); \ + } while(0) + +static void +blake2s_compress (blake2s_state_t * S, const uint8_t in[BLAKE2S_BLOCK_BYTES]) +{ + uint32_t m[16]; + uint32_t v[16]; + size_t i; + + for (i = 0; i < 16; ++i) + { + m[i] = load32 (in + i * sizeof (m[i])); + } + + for (i = 0; i < 8; ++i) + { + v[i] = S->h[i]; + } + + v[8] = blake2s_IV[0]; + v[9] = blake2s_IV[1]; + v[10] = blake2s_IV[2]; + v[11] = blake2s_IV[3]; + v[12] = S->t[0] ^ blake2s_IV[4]; + v[13] = S->t[1] ^ blake2s_IV[5]; + v[14] = S->f[0] ^ blake2s_IV[6]; + v[15] = S->f[1] ^ blake2s_IV[7]; + + ROUND (0); + ROUND (1); + ROUND (2); + ROUND (3); + ROUND (4); + ROUND (5); + ROUND (6); + ROUND (7); + ROUND (8); + ROUND (9); + + for (i = 0; i < 8; ++i) + { + S->h[i] = S->h[i] ^ v[i] ^ v[i + 8]; + } +} + +#undef G +#undef ROUND + +int +blake2s_update (blake2s_state_t * S, const void *pin, size_t inlen) +{ + const unsigned char *in = (const unsigned char *) pin; + if (inlen > 0) + { + size_t left = S->buflen; + size_t fill = BLAKE2S_BLOCK_BYTES - left; + if (inlen > fill) + { + S->buflen = 0; + memcpy (S->buf + left, in, fill); /* Fill buffer */ + blake2s_increment_counter (S, BLAKE2S_BLOCK_BYTES); + blake2s_compress (S, S->buf); /* Compress */ + in += fill; + inlen -= fill; + while (inlen > BLAKE2S_BLOCK_BYTES) + { + blake2s_increment_counter (S, BLAKE2S_BLOCK_BYTES); + blake2s_compress (S, in); + in += BLAKE2S_BLOCK_BYTES; + inlen -= BLAKE2S_BLOCK_BYTES; + } + } + memcpy (S->buf + S->buflen, in, inlen); + S->buflen += inlen; + } + return 0; +} + +int +blake2s_final (blake2s_state_t * S, void *out, size_t outlen) +{ + uint8_t buffer[BLAKE2S_OUT_BYTES] = { 0 }; + size_t i; + + if (out == NULL || outlen < S->outlen) + return -1; + + if (blake2s_is_lastblock (S)) + return -1; + + blake2s_increment_counter (S, (uint32_t) S->buflen); + blake2s_set_lastblock (S); + memset (S->buf + S->buflen, 0, BLAKE2S_BLOCK_BYTES - S->buflen); /* Padding */ + blake2s_compress (S, S->buf); + + for (i = 0; i < 8; ++i) /* Output full hash to temp buffer */ + store32 (buffer + sizeof (S->h[i]) * i, S->h[i]); + + memcpy (out, buffer, outlen); + secure_zero_memory (buffer, sizeof (buffer)); + return 0; +} + +int +blake2s (void *out, size_t outlen, const void *in, size_t inlen, + const void *key, size_t keylen) +{ + blake2s_state_t S[1]; + + /* Verify parameters */ + if (NULL == in && inlen > 0) + return -1; + + if (NULL == out) + return -1; + + if (NULL == key && keylen > 0) + return -1; + + if (!outlen || outlen > BLAKE2S_OUT_BYTES) + return -1; + + if (keylen > BLAKE2S_KEY_BYTES) + return -1; + + if (keylen > 0) + { + if (blake2s_init_key (S, outlen, key, keylen) < 0) + return -1; + } + else + { + if (blake2s_init (S, outlen) < 0) + return -1; + } + + blake2s_update (S, (const uint8_t *) in, inlen); + blake2s_final (S, out, outlen); + return 0; +} + +/* + * fd.io coding-style-patch-verification: ON + * + * Local Variables: + * eval: (c-set-style "gnu") + * End: + */ diff --git a/src/plugins/wireguard/blake/blake2s.h b/src/plugins/wireguard/blake/blake2s.h new file mode 100755 index 00000000000..37da0acf28a --- /dev/null +++ b/src/plugins/wireguard/blake/blake2s.h @@ -0,0 +1,90 @@ +/* + * Copyright (c) 2020 Doc.ai and/or its affiliates. + * Copyright (c) 2012 Samuel Neves <sneves@dei.uc.pt>. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +/* + More information about the BLAKE2 hash function can be found at + https://blake2.net. +*/ + +#ifndef __included_crypto_blake2s_h__ +#define __included_crypto_blake2s_h__ + +#include <vlib/vlib.h> + +#if defined(_MSC_VER) +#define BLAKE2_PACKED(x) __pragma(pack(push, 1)) x __pragma(pack(pop)) +#else +#define BLAKE2_PACKED(x) x __attribute__((packed)) +#endif + +enum blake2s_constant +{ + BLAKE2S_BLOCK_BYTES = 64, + BLAKE2S_OUT_BYTES = 32, + BLAKE2S_KEY_BYTES = 32, + BLAKE2S_HASH_SIZE = BLAKE2S_OUT_BYTES, + BLAKE2S_SALT_BYTES = 8, + BLAKE2S_PERSONAL_BYTES = 8 +}; + +typedef struct blake2s_state +{ + uint32_t h[8]; + uint32_t t[2]; + uint32_t f[2]; + uint8_t buf[BLAKE2S_BLOCK_BYTES]; + size_t buflen; + size_t outlen; + uint8_t last_node; +} blake2s_state_t; + +BLAKE2_PACKED (struct blake2s_param + { + uint8_t digest_length; /* 1 */ + uint8_t key_length; /* 2 */ + uint8_t fanout; /* 3 */ + uint8_t depth; /* 4 */ + uint32_t leaf_length; /* 8 */ + uint32_t node_offset; /* 12 */ + uint16_t xof_length; /* 14 */ + uint8_t node_depth; /* 15 */ + uint8_t inner_length; /* 16 */ + /* uint8_t reserved[0]; */ + uint8_t salt[BLAKE2S_SALT_BYTES]; /* 24 */ + uint8_t personal[BLAKE2S_PERSONAL_BYTES]; /* 32 */ + }); + +typedef struct blake2s_param blake2s_param_t; + +/* Streaming API */ +int blake2s_init (blake2s_state_t * S, size_t outlen); +int blake2s_init_key (blake2s_state_t * S, size_t outlen, const void *key, + size_t keylen); +int blake2s_init_param (blake2s_state_t * S, const blake2s_param_t * P); +int blake2s_update (blake2s_state_t * S, const void *in, size_t inlen); +int blake2s_final (blake2s_state_t * S, void *out, size_t outlen); + +int blake2s (void *out, size_t outlen, const void *in, size_t inlen, + const void *key, size_t keylen); + +#endif /* __included_crypto_blake2s_h__ */ + +/* + * fd.io coding-style-patch-verification: ON + * + * Local Variables: + * eval: (c-set-style "gnu") + * End: + */ diff --git a/src/plugins/wireguard/test/test_wireguard.py b/src/plugins/wireguard/test/test_wireguard.py new file mode 100755 index 00000000000..cd124f3e246 --- /dev/null +++ b/src/plugins/wireguard/test/test_wireguard.py @@ -0,0 +1,292 @@ +#!/usr/bin/env python3 +""" Wg tests """ + +from scapy.packet import Packet +from scapy.packet import Raw +from scapy.layers.l2 import Ether +from scapy.layers.inet import IP, UDP +from scapy.contrib.wireguard import Wireguard, WireguardResponse, \ + WireguardInitiation +from cryptography.hazmat.primitives.asymmetric.x25519 import X25519PrivateKey +from cryptography.hazmat.primitives.serialization import Encoding, \ + PrivateFormat, PublicFormat, NoEncryption + +from vpp_ipip_tun_interface import VppIpIpTunInterface +from vpp_interface import VppInterface +from vpp_object import VppObject +from framework import VppTestCase +from re import compile +import unittest + +""" TestWg is a subclass of VPPTestCase classes. + +Wg test. + +""" + + +class VppWgInterface(VppInterface): + """ + VPP WireGuard interface + """ + + def __init__(self, test, src, port, key=None): + super(VppWgInterface, self).__init__(test) + + self.key = key + if not self.key: + self.generate = True + else: + self.generate = False + self.port = port + self.src = src + + def add_vpp_config(self): + r = self.test.vapi.wireguard_interface_create(interface={ + 'user_instance': 0xffffffff, + 'port': self.port, + 'src_ip': self.src, + 'private_key': self.key_bytes() + }) + self.set_sw_if_index(r.sw_if_index) + self.test.registry.register(self, self.test.logger) + return self + + def key_bytes(self): + if self.key: + return self.key.private_bytes(Encoding.Raw, + PrivateFormat.Raw, + NoEncryption()) + else: + return bytearray(32) + + def remove_vpp_config(self): + self.test.vapi.wireguard_interface_delete( + sw_if_index=self._sw_if_index) + + def query_vpp_config(self): + ts = self.test.vapi.wireguard_interface_dump(sw_if_index=0xffffffff) + for t in ts: + if t.interface.sw_if_index == self._sw_if_index and \ + str(t.interface.src_ip) == self.src and \ + t.interface.port == self.port and \ + t.interface.private_key == self.key_bytes(): + return True + return False + + def __str__(self): + return self.object_id() + + def object_id(self): + return "wireguard-%d" % self._sw_if_index + + +def find_route(test, prefix, table_id=0): + routes = test.vapi.ip_route_dump(table_id, False) + + for e in routes: + if table_id == e.route.table_id \ + and str(e.route.prefix) == str(prefix): + return True + return False + + +class VppWgPeer(VppObject): + + def __init__(self, + test, + itf, + endpoint, + port, + allowed_ips, + persistent_keepalive=15): + self._test = test + self.itf = itf + self.endpoint = endpoint + self.port = port + self.allowed_ips = allowed_ips + self.persistent_keepalive = persistent_keepalive + self.private_key = X25519PrivateKey.generate() + self.public_key = self.private_key.public_key() + self.hash = bytearray(16) + + def validate_routing(self): + for a in self.allowed_ips: + self._test.assertTrue(find_route(self._test, a)) + + def validate_no_routing(self): + for a in self.allowed_ips: + self._test.assertFalse(find_route(self._test, a)) + + def add_vpp_config(self): + rv = self._test.vapi.wireguard_peer_add( + peer={ + 'public_key': self.public_key_bytes(), + 'port': self.port, + 'endpoint': self.endpoint, + 'n_allowed_ips': len(self.allowed_ips), + 'allowed_ips': self.allowed_ips, + 'sw_if_index': self.itf.sw_if_index, + 'persistent_keepalive': self.persistent_keepalive}) + self.index = rv.peer_index + self._test.registry.register(self, self._test.logger) + self.validate_routing() + return self + + def remove_vpp_config(self): + self._test.vapi.wireguard_peer_remove(peer_index=self.index) + self.validate_no_routing() + + def object_id(self): + return ("wireguard-peer-%s" % self.index) + + def public_key_bytes(self): + return self.public_key.public_bytes(Encoding.Raw, + PublicFormat.Raw) + + def private_key_bytes(self): + return self.private_key.private_bytes(Encoding.Raw, + PrivateFormat.Raw, + NoEncryption()) + + def query_vpp_config(self): + peers = self._test.vapi.wireguard_peers_dump() + + for p in peers: + if p.peer.public_key == self.public_key_bytes() and \ + p.peer.port == self.port and \ + str(p.peer.endpoint) == self.endpoint and \ + p.peer.sw_if_index == self.itf.sw_if_index and \ + len(self.allowed_ips) == p.peer.n_allowed_ips: + self.allowed_ips.sort() + p.peer.allowed_ips.sort() + + for (a1, a2) in zip(self.allowed_ips, p.peer.allowed_ips): + if str(a1) != str(a2): + return False + return True + return False + + +class TestWg(VppTestCase): + """ Wireguard Test Case """ + + error_str = compile(r"Error") + + @classmethod + def setUpClass(cls): + super(TestWg, cls).setUpClass() + try: + cls.create_pg_interfaces(range(3)) + for i in cls.pg_interfaces: + i.admin_up() + i.config_ip4() + i.resolve_arp() + + except Exception: + super(TestWg, cls).tearDownClass() + raise + + @classmethod + def tearDownClass(cls): + super(TestWg, cls).tearDownClass() + + def test_wg_interface(self): + port = 12312 + + # Create interface + wg0 = VppWgInterface(self, + self.pg1.local_ip4, + port).add_vpp_config() + + self.logger.info(self.vapi.cli("sh int")) + + # delete interface + wg0.remove_vpp_config() + + def test_wg_peer(self): + wg_output_node_name = '/err/wg-output-tun/' + wg_input_node_name = '/err/wg-input/' + + port = 12323 + + # Create interfaces + wg0 = VppWgInterface(self, + self.pg1.local_ip4, + port, + key=X25519PrivateKey.generate()).add_vpp_config() + wg1 = VppWgInterface(self, + self.pg2.local_ip4, + port+1).add_vpp_config() + wg0.admin_up() + wg1.admin_up() + + # Check peer counter + self.assertEqual(len(self.vapi.wireguard_peers_dump()), 0) + + self.pg_enable_capture(self.pg_interfaces) + self.pg_start() + + peer_1 = VppWgPeer(self, + wg0, + self.pg1.remote_ip4, + port+1, + ["10.11.2.0/24", + "10.11.3.0/24"]).add_vpp_config() + self.assertEqual(len(self.vapi.wireguard_peers_dump()), 1) + + # wait for the peer to send a handshake + capture = self.pg1.get_capture(1, timeout=2) + handshake = capture[0] + + self.assertEqual(handshake[IP].src, wg0.src) + self.assertEqual(handshake[IP].dst, peer_1.endpoint) + self.assertEqual(handshake[UDP].sport, wg0.port) + self.assertEqual(handshake[UDP].dport, peer_1.port) + handshake = Wireguard(handshake[Raw]) + self.assertEqual(handshake.message_type, 1) # "initiate") + init = handshake[WireguardInitiation] + + # route a packet into the wg interface + # use the allowed-ip prefix + p = (Ether(dst=self.pg0.local_mac, src=self.pg0.remote_mac) / + IP(src=self.pg0.remote_ip4, dst="10.11.3.2") / + UDP(sport=555, dport=556) / + Raw()) + # rx = self.send_and_expect(self.pg0, [p], self.pg1) + rx = self.send_and_assert_no_replies(self.pg0, [p]) + + self.logger.info(self.vapi.cli("sh error")) + init_sent = wg_output_node_name + "Keypair error" + self.assertEqual(1, self.statistics.get_err_counter(init_sent)) + + # Create many peers on sencond interface + NUM_PEERS = 16 + self.pg2.generate_remote_hosts(NUM_PEERS) + self.pg2.configure_ipv4_neighbors() + + peers = [] + for i in range(NUM_PEERS): + peers.append(VppWgPeer(self, + wg1, + self.pg2.remote_hosts[i].ip4, + port+1+i, + ["10.10.%d.4/32" % i]).add_vpp_config()) + self.assertEqual(len(self.vapi.wireguard_peers_dump()), i+2) + + self.logger.info(self.vapi.cli("show wireguard peer")) + self.logger.info(self.vapi.cli("show wireguard interface")) + self.logger.info(self.vapi.cli("show adj 37")) + self.logger.info(self.vapi.cli("sh ip fib 172.16.3.17")) + self.logger.info(self.vapi.cli("sh ip fib 10.11.3.0")) + + # remove peers + for p in peers: + self.assertTrue(p.query_vpp_config()) + p.remove_vpp_config() + self.assertEqual(len(self.vapi.wireguard_peers_dump()), 1) + peer_1.remove_vpp_config() + self.assertEqual(len(self.vapi.wireguard_peers_dump()), 0) + + wg0.remove_vpp_config() + # wg1.remove_vpp_config() diff --git a/src/plugins/wireguard/wireguard.api b/src/plugins/wireguard/wireguard.api new file mode 100755 index 00000000000..195755e5c43 --- /dev/null +++ b/src/plugins/wireguard/wireguard.api @@ -0,0 +1,162 @@ +/* Hey Emacs use -*- mode: C -*- */ +/* + * Copyright (c) 2020 Doc.ai and/or its affiliates. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +option version = "0.1.0"; + +import "vnet/interface_types.api"; +import "vnet/ip/ip_types.api"; + +/** \brief Create wireguard interface + @param client_index - opaque cookie to identify the sender + @param context - sender context, to match reply w/ request + @param private_key - private key in binary format of this device + @param port - port of this device + @param src_ip - packet sent through this interface us this + address as the IP source. +*/ +typedef wireguard_interface +{ + u32 user_instance [default=0xffffffff]; + vl_api_interface_index_t sw_if_index; + u8 private_key[32]; + u16 port; + vl_api_address_t src_ip; +}; + +/** \brief Create an Wireguard interface + */ +define wireguard_interface_create { + u32 client_index; + u32 context; + vl_api_wireguard_interface_t interface; + bool generate_key; +}; + +/** \brief Add Wireguard interface interface response + @param context - sender context, to match reply w/ request + @param retval - return status + @param sw_if_index - sw_if_index of new interface (for successful add) +*/ +define wireguard_interface_create_reply +{ + u32 context; + i32 retval; + vl_api_interface_index_t sw_if_index; +}; + +autoreply define wireguard_interface_delete +{ + u32 client_index; + u32 context; + vl_api_interface_index_t sw_if_index; +}; + +define wireguard_interface_dump +{ + u32 client_index; + u32 context; + bool show_private_key; + vl_api_interface_index_t sw_if_index; +}; + +define wireguard_interface_details +{ + u32 context; + vl_api_wireguard_interface_t interface; +}; + +enum wireguard_peer_flags : u8 +{ + WIREGUARD_PEER_STATUS_DEAD = 0x1, +}; + +/** \brief Create new peer + @param public_key - public key (in binary format) of destination peer + @param port - destination port + @param table_id - The IP table in which 'endpoint' is reachable + @param endpoint - destination ip + @param allowed_ip - allowed incoming ip tunnel + @param tun_sw_if_index - tunnel interface + @param persistent_keepalive - keepalive packet timeout +*/ +typedef wireguard_peer +{ + u8 public_key[32]; + u16 port; + u16 persistent_keepalive; + u32 table_id; + vl_api_address_t endpoint; + vl_api_interface_index_t sw_if_index; + vl_api_wireguard_peer_flags_t flags; + u8 n_allowed_ips; + vl_api_prefix_t allowed_ips[n_allowed_ips]; +}; + +/** \brief Create new peer + @param client_index - opaque cookie to identify the sender + @param context - sender context, to match reply w/ request + @param peer - peer to create +*/ +define wireguard_peer_add +{ + u32 client_index; + u32 context; + vl_api_wireguard_peer_t peer; +}; +define wireguard_peer_add_reply +{ + u32 context; + i32 retval; + u32 peer_index; +}; + +/** \brief Remove peer by public_key + @param client_index - opaque cookie to identify the sender + @param context - sender context, to match reply w/ request + @param public_key +*/ +autoreply define wireguard_peer_remove +{ + u32 client_index; + u32 context; + u32 peer_index; +}; + +/** \brief Dump all peers + @param client_index - opaque cookie to identify the sender + @param context - sender context, to match reply w/ request +*/ +define wireguard_peers_dump { + u32 client_index; + u32 context; +}; + +/** \brief Dump peers response + @param context - sender context, to match reply w/ request + @param is_dead - is peer valid yet + @param public_key - peer public_key + @param ip4_address - ip4 endpoint address +*/ +define wireguard_peers_details { + u32 context; + vl_api_wireguard_peer_t peer; +}; + +/* + * Local Variables: + * eval: (c-set-style "gnu") + * End: + */ diff --git a/src/plugins/wireguard/wireguard.c b/src/plugins/wireguard/wireguard.c new file mode 100755 index 00000000000..00921811e4a --- /dev/null +++ b/src/plugins/wireguard/wireguard.c @@ -0,0 +1,63 @@ +/* + * Copyright (c) 2020 Doc.ai and/or its affiliates. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include <vnet/vnet.h> +#include <vnet/plugin/plugin.h> +#include <vnet/ipip/ipip.h> +#include <vpp/app/version.h> +#include <vnet/udp/udp.h> + +#include <wireguard/wireguard_send.h> +#include <wireguard/wireguard_key.h> +#include <wireguard/wireguard_if.h> +#include <wireguard/wireguard.h> + +wg_main_t wg_main; + +static clib_error_t * +wg_init (vlib_main_t * vm) +{ + wg_main_t *wmp = &wg_main; + + wmp->vlib_main = vm; + wmp->peers = 0; + + return (NULL); +} + +VLIB_INIT_FUNCTION (wg_init); + +/* *INDENT-OFF* */ + +VNET_FEATURE_INIT (wg_output_tun, static) = +{ + .arc_name = "ip4-output", + .node_name = "wg-output-tun", +}; + +VLIB_PLUGIN_REGISTER () = +{ + .version = VPP_BUILD_VER, + .description = "Wireguard Protocol", +}; +/* *INDENT-ON* */ + +/* + * fd.io coding-style-patch-verification: ON + * + * Local Variables: + * eval: (c-set-style "gnu") + * End: + */ diff --git a/src/plugins/wireguard/wireguard.h b/src/plugins/wireguard/wireguard.h new file mode 100755 index 00000000000..70a692e602f --- /dev/null +++ b/src/plugins/wireguard/wireguard.h @@ -0,0 +1,50 @@ +/* + * Copyright (c) 2020 Doc.ai and/or its affiliates. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#ifndef __included_wg_h__ +#define __included_wg_h__ + +#include <wireguard/wireguard_index_table.h> +#include <wireguard/wireguard_messages.h> +#include <wireguard/wireguard_peer.h> + +extern vlib_node_registration_t wg_input_node; +extern vlib_node_registration_t wg_output_tun_node; + + + +typedef struct +{ + /* convenience */ + vlib_main_t *vlib_main; + + u16 msg_id_base; + + // Peers pool + wg_peer_t *peers; + wg_index_table_t index_table; + +} wg_main_t; + +extern wg_main_t wg_main; + +#endif /* __included_wg_h__ */ + +/* + * fd.io coding-style-patch-verification: ON + * + * Local Variables: + * eval: (c-set-style "gnu") + * End: + */ diff --git a/src/plugins/wireguard/wireguard_api.c b/src/plugins/wireguard/wireguard_api.c new file mode 100755 index 00000000000..e107cb56b4b --- /dev/null +++ b/src/plugins/wireguard/wireguard_api.c @@ -0,0 +1,278 @@ +/* + * Copyright (c) 2020 Doc.ai and/or its affiliates. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include <vnet/vnet.h> +#include <vlibmemory/api.h> + +#include <vnet/format_fns.h> +#include <vnet/ip/ip_types_api.h> +#include <vlibapi/api.h> + +#include <wireguard/wireguard.api_enum.h> +#include <wireguard/wireguard.api_types.h> + +#include <wireguard/wireguard_key.h> +#include <wireguard/wireguard.h> +#include <wireguard/wireguard_if.h> +#include <wireguard/wireguard_peer.h> + +#define REPLY_MSG_ID_BASE wmp->msg_id_base +#include <vlibapi/api_helper_macros.h> + +static void + vl_api_wireguard_interface_create_t_handler + (vl_api_wireguard_interface_create_t * mp) +{ + vl_api_wireguard_interface_create_reply_t *rmp; + wg_main_t *wmp = &wg_main; + u8 private_key[NOISE_PUBLIC_KEY_LEN]; + ip_address_t src; + u32 sw_if_index; + int rv = 0; + + ip_address_decode2 (&mp->interface.src_ip, &src); + + if (AF_IP6 == ip_addr_version (&src)) + rv = VNET_API_ERROR_INVALID_PROTOCOL; + else + { + if (mp->generate_key) + curve25519_gen_secret (private_key); + else + clib_memcpy (private_key, mp->interface.private_key, + NOISE_PUBLIC_KEY_LEN); + + rv = wg_if_create (ntohl (mp->interface.user_instance), private_key, + ntohs (mp->interface.port), &src, &sw_if_index); + } + + /* *INDENT-OFF* */ + REPLY_MACRO2(VL_API_WIREGUARD_INTERFACE_CREATE_REPLY, + { + rmp->sw_if_index = htonl(sw_if_index); + }); + /* *INDENT-ON* */ +} + +static void + vl_api_wireguard_interface_delete_t_handler + (vl_api_wireguard_interface_delete_t * mp) +{ + vl_api_wireguard_interface_delete_reply_t *rmp; + wg_main_t *wmp = &wg_main; + int rv = 0; + + VALIDATE_SW_IF_INDEX (mp); + + rv = wg_if_delete (ntohl (mp->sw_if_index)); + + BAD_SW_IF_INDEX_LABEL; + + /* *INDENT-OFF* */ + REPLY_MACRO(VL_API_WIREGUARD_INTERFACE_DELETE_REPLY); + /* *INDENT-ON* */ +} + +typedef struct wg_deatils_walk_t_ +{ + vl_api_registration_t *reg; + u32 context; +} wg_deatils_walk_t; + +static walk_rc_t +wireguard_if_send_details (index_t wgii, void *data) +{ + vl_api_wireguard_interface_details_t *rmp; + wg_deatils_walk_t *ctx = data; + const wg_if_t *wgi; + + wgi = wg_if_get (wgii); + + rmp = vl_msg_api_alloc_zero (sizeof (*rmp)); + rmp->_vl_msg_id = htons (VL_API_WIREGUARD_INTERFACE_DETAILS + + wg_main.msg_id_base); + + clib_memcpy (rmp->interface.private_key, + wgi->local.l_private, NOISE_PUBLIC_KEY_LEN); + rmp->interface.sw_if_index = htonl (wgi->sw_if_index); + rmp->interface.port = htons (wgi->port); + ip_address_encode2 (&wgi->src_ip, &rmp->interface.src_ip); + + rmp->context = ctx->context; + + vl_api_send_msg (ctx->reg, (u8 *) rmp); + + return (WALK_CONTINUE); +} + +static void +vl_api_wireguard_interface_dump_t_handler (vl_api_wireguard_interface_dump_t * + mp) +{ + vl_api_registration_t *reg; + + reg = vl_api_client_index_to_registration (mp->client_index); + if (reg == 0) + return; + + wg_deatils_walk_t ctx = { + .reg = reg, + .context = mp->context, + }; + + wg_if_walk (wireguard_if_send_details, &ctx); +} + +static void +vl_api_wireguard_peer_add_t_handler (vl_api_wireguard_peer_add_t * mp) +{ + vl_api_wireguard_peer_add_reply_t *rmp; + wg_main_t *wmp = &wg_main; + index_t peeri; + int ii, rv = 0; + + ip_address_t endpoint; + fib_prefix_t *allowed_ips = NULL; + + VALIDATE_SW_IF_INDEX (&(mp->peer)); + + if (0 == mp->peer.n_allowed_ips) + { + rv = VNET_API_ERROR_INVALID_VALUE; + goto done; + } + + vec_validate (allowed_ips, mp->peer.n_allowed_ips - 1); + ip_address_decode2 (&mp->peer.endpoint, &endpoint); + + for (ii = 0; ii < mp->peer.n_allowed_ips; ii++) + ip_prefix_decode (&mp->peer.allowed_ips[ii], &allowed_ips[ii]); + + if (AF_IP6 == ip_addr_version (&endpoint) || + FIB_PROTOCOL_IP6 == allowed_ips[0].fp_proto) + /* ip6 currently not supported, but the API needs to support it + * else we'll need to change it later, and that's a PITA */ + rv = VNET_API_ERROR_INVALID_PROTOCOL; + else + rv = wg_peer_add (ntohl (mp->peer.sw_if_index), + mp->peer.public_key, + ntohl (mp->peer.table_id), + &ip_addr_46 (&endpoint), + allowed_ips, + ntohs (mp->peer.port), + ntohs (mp->peer.persistent_keepalive), &peeri); + + vec_free (allowed_ips); +done: + BAD_SW_IF_INDEX_LABEL; + /* *INDENT-OFF* */ + REPLY_MACRO2(VL_API_WIREGUARD_PEER_ADD_REPLY, + { + rmp->peer_index = ntohl (peeri); + }); + /* *INDENT-ON* */ +} + +static void +vl_api_wireguard_peer_remove_t_handler (vl_api_wireguard_peer_remove_t * mp) +{ + vl_api_wireguard_peer_remove_reply_t *rmp; + wg_main_t *wmp = &wg_main; + int rv = 0; + + rv = wg_peer_remove (ntohl (mp->peer_index)); + + /* *INDENT-OFF* */ + REPLY_MACRO(VL_API_WIREGUARD_PEER_REMOVE_REPLY); + /* *INDENT-ON* */ +} + +static walk_rc_t +send_wg_peers_details (index_t peeri, void *data) +{ + vl_api_wireguard_peers_details_t *rmp; + wg_deatils_walk_t *ctx = data; + const wg_peer_t *peer; + u8 n_allowed_ips; + size_t ss; + + peer = wg_peer_get (peeri); + n_allowed_ips = vec_len (peer->allowed_ips); + + ss = (sizeof (*rmp) + (n_allowed_ips * sizeof (rmp->peer.allowed_ips[0]))); + + rmp = vl_msg_api_alloc_zero (ss); + + rmp->_vl_msg_id = htons (VL_API_WIREGUARD_PEERS_DETAILS + + wg_main.msg_id_base); + + if (peer->is_dead) + rmp->peer.flags = WIREGUARD_PEER_STATUS_DEAD; + clib_memcpy (rmp->peer.public_key, + peer->remote.r_public, NOISE_PUBLIC_KEY_LEN); + + ip_address_encode (&peer->dst.addr, IP46_TYPE_ANY, &rmp->peer.endpoint); + rmp->peer.port = htons (peer->dst.port); + rmp->peer.n_allowed_ips = n_allowed_ips; + rmp->peer.sw_if_index = htonl (peer->wg_sw_if_index); + + int ii; + for (ii = 0; ii < n_allowed_ips; ii++) + ip_prefix_encode (&peer->allowed_ips[ii].prefix, + &rmp->peer.allowed_ips[ii]); + + rmp->context = ctx->context; + + vl_api_send_msg (ctx->reg, (u8 *) rmp); + + return (WALK_CONTINUE); +} + +static void +vl_api_wireguard_peers_dump_t_handler (vl_api_wireguard_peers_dump_t * mp) +{ + vl_api_registration_t *reg; + + reg = vl_api_client_index_to_registration (mp->client_index); + if (reg == NULL) + return; + + wg_deatils_walk_t ctx = { + .reg = reg, + .context = mp->context, + }; + + wg_peer_walk (send_wg_peers_details, &ctx); +} + +/* set tup the API message handling tables */ +#include <wireguard/wireguard.api.c> +static clib_error_t * +wg_api_hookup (vlib_main_t * vm) +{ + wg_main_t *wmp = &wg_main; + wmp->msg_id_base = setup_message_id_table (); + return 0; +} + +VLIB_API_INIT_FUNCTION (wg_api_hookup); + +/* + * fd.io coding-style-patch-verification: ON + * + * Local Variables: + * eval: (c-set-style "gnu") + * End: + */ diff --git a/src/plugins/wireguard/wireguard_cli.c b/src/plugins/wireguard/wireguard_cli.c new file mode 100755 index 00000000000..7fdccdc64b3 --- /dev/null +++ b/src/plugins/wireguard/wireguard_cli.c @@ -0,0 +1,358 @@ +/* + * Copyright (c) 2020 Doc.ai and/or its affiliates. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include <wireguard/wireguard.h> +#include <wireguard/wireguard_key.h> +#include <wireguard/wireguard_peer.h> +#include <wireguard/wireguard_if.h> + +static clib_error_t * +wg_if_create_cli (vlib_main_t * vm, + unformat_input_t * input, vlib_cli_command_t * cmd) +{ + unformat_input_t _line_input, *line_input = &_line_input; + u8 private_key[NOISE_PUBLIC_KEY_LEN]; + u32 instance, sw_if_index; + ip_address_t src_ip; + clib_error_t *error; + u8 *private_key_64; + u32 port, generate_key = 0; + int rv; + + error = NULL; + instance = sw_if_index = ~0; + private_key_64 = 0; + port = 0; + + if (unformat_user (input, unformat_line_input, line_input)) + { + while (unformat_check_input (line_input) != UNFORMAT_END_OF_INPUT) + { + if (unformat (line_input, "instance %d", &instance)) + ; + else if (unformat (line_input, "private-key %s", &private_key_64)) + { + if (!(key_from_base64 (private_key_64, + NOISE_KEY_LEN_BASE64, private_key))) + { + error = clib_error_return (0, "Error parsing private key"); + break; + } + } + else if (unformat (line_input, "listen-port %d", &port)) + ; + else if (unformat (line_input, "port %d", &port)) + ; + else if (unformat (line_input, "generate-key")) + generate_key = 1; + else + if (unformat (line_input, "src %U", unformat_ip_address, &src_ip)) + ; + else + { + error = clib_error_return (0, "unknown input: %U", + format_unformat_error, line_input); + break; + } + } + + unformat_free (line_input); + + if (error) + return error; + } + + if (generate_key) + curve25519_gen_secret (private_key); + + rv = wg_if_create (instance, private_key, port, &src_ip, &sw_if_index); + + if (rv) + return clib_error_return (0, "wireguard interface create failed"); + + vlib_cli_output (vm, "%U\n", format_vnet_sw_if_index_name, vnet_get_main (), + sw_if_index); + return 0; +} + +/*? + * Create a Wireguard interface. +?*/ +/* *INDENT-OFF* */ +VLIB_CLI_COMMAND (wg_if_create_command, static) = { + .path = "wireguard create", + .short_help = "wireguard create listen-port <port> " + "private-key <key> src <IP> [generate-key]", + .function = wg_if_create_cli, +}; +/* *INDENT-ON* */ + +static clib_error_t * +wg_if_delete_cli (vlib_main_t * vm, + unformat_input_t * input, vlib_cli_command_t * cmd) +{ + vnet_main_t *vnm; + u32 sw_if_index; + int rv; + + vnm = vnet_get_main (); + sw_if_index = ~0; + + while (unformat_check_input (input) != UNFORMAT_END_OF_INPUT) + { + if (unformat + (input, "%U", unformat_vnet_sw_interface, vnm, &sw_if_index)) + ; + else + break; + } + + if (~0 != sw_if_index) + { + rv = wg_if_delete (sw_if_index); + + if (rv) + return clib_error_return (0, "wireguard interface delete failed"); + } + else + return clib_error_return (0, "no such interface: %U", + format_unformat_error, input); + + return 0; +} + +/*? + * Delete a Wireguard interface. +?*/ +/* *INDENT-OFF* */ +VLIB_CLI_COMMAND (wg_if_delete_command, static) = { + .path = "wireguard delete", + .short_help = "wireguard delete <interface>", + .function = wg_if_delete_cli, +}; +/* *INDENT-ON* */ + + +static clib_error_t * +wg_peer_add_command_fn (vlib_main_t * vm, + unformat_input_t * input, vlib_cli_command_t * cmd) +{ + vnet_main_t *vnm = vnet_get_main (); + clib_error_t *error = NULL; + unformat_input_t _line_input, *line_input = &_line_input; + + u8 *public_key_64 = 0; + u8 public_key[NOISE_PUBLIC_KEY_LEN]; + fib_prefix_t allowed_ip, *allowed_ips = NULL; + ip_prefix_t pfx; + ip_address_t ip; + u32 portDst = 0, table_id = 0; + u32 persistent_keepalive = 0; + u32 tun_sw_if_index = ~0; + u32 peer_index; + int rv; + + if (!unformat_user (input, unformat_line_input, line_input)) + return 0; + + while (unformat_check_input (line_input) != UNFORMAT_END_OF_INPUT) + { + if (unformat (line_input, "public-key %s", &public_key_64)) + { + if (!(key_from_base64 (public_key_64, + NOISE_KEY_LEN_BASE64, public_key))) + { + error = clib_error_return (0, "Error parsing private key"); + goto done; + } + } + else if (unformat (line_input, "endpoint %U", unformat_ip_address, &ip)) + ; + else if (unformat (line_input, "table-id %d", &table_id)) + ; + else if (unformat (line_input, "port %d", &portDst)) + ; + else if (unformat (line_input, "persistent-keepalive %d", + &persistent_keepalive)) + ; + else if (unformat (line_input, "allowed-ip %U", + unformat_ip_prefix, &pfx)) + { + ip_prefix_to_fib_prefix (&pfx, &allowed_ip); + vec_add1 (allowed_ips, allowed_ip); + } + else if (unformat (line_input, "%U", + unformat_vnet_sw_interface, vnm, &tun_sw_if_index)) + ; + else + { + error = clib_error_return (0, "Input error"); + goto done; + } + } + + if (AF_IP6 == ip_addr_version (&ip) || + FIB_PROTOCOL_IP6 == allowed_ip.fp_proto) + rv = VNET_API_ERROR_INVALID_PROTOCOL; + else + rv = wg_peer_add (tun_sw_if_index, + public_key, + table_id, + &ip_addr_46 (&ip), + allowed_ips, + portDst, persistent_keepalive, &peer_index); + + switch (rv) + { + case VNET_API_ERROR_KEY_LENGTH: + error = clib_error_return (0, "Error parsing public key"); + break; + case VNET_API_ERROR_ENTRY_ALREADY_EXISTS: + error = clib_error_return (0, "Peer already exist"); + break; + case VNET_API_ERROR_INVALID_SW_IF_INDEX: + error = clib_error_return (0, "Tunnel is not specified"); + break; + case VNET_API_ERROR_LIMIT_EXCEEDED: + error = clib_error_return (0, "Max peers limit"); + break; + case VNET_API_ERROR_INIT_FAILED: + error = clib_error_return (0, "wireguard device parameters is not set"); + break; + case VNET_API_ERROR_INVALID_PROTOCOL: + error = clib_error_return (0, "ipv6 not supported yet"); + break; + } + +done: + vec_free (public_key_64); + vec_free (allowed_ips); + unformat_free (line_input); + return error; +} + +/* *INDENT-OFF* */ +VLIB_CLI_COMMAND (wg_peer_add_command, static) = +{ + .path = "wireguard peer add", + .short_help = "wireguard peer add <wg_int> public-key <pub_key_other>" + "endpoint <ip4_dst> allowed-ip <prefix>" + "dst-port [port_dst] persistent-keepalive [keepalive_interval]", + .function = wg_peer_add_command_fn, +}; +/* *INDENT-ON* */ + +static clib_error_t * +wg_peer_remove_command_fn (vlib_main_t * vm, + unformat_input_t * input, vlib_cli_command_t * cmd) +{ + clib_error_t *error = NULL; + u32 peer_index; + int rv; + + unformat_input_t _line_input, *line_input = &_line_input; + if (!unformat_user (input, unformat_line_input, line_input)) + return 0; + + if (unformat (line_input, "%d", &peer_index)) + ; + else + { + error = clib_error_return (0, "Input error"); + goto done; + } + + rv = wg_peer_remove (peer_index); + + switch (rv) + { + case VNET_API_ERROR_KEY_LENGTH: + error = clib_error_return (0, "Error parsing public key"); + break; + } + +done: + unformat_free (line_input); + return error; +} + +/* *INDENT-OFF* */ +VLIB_CLI_COMMAND (wg_peer_remove_command, static) = +{ + .path = "wireguard peer remove", + .short_help = "wireguard peer remove <index>", + .function = wg_peer_remove_command_fn, +}; +/* *INDENT-ON* */ + +static walk_rc_t +wg_peer_show_one (index_t peeri, void *arg) +{ + vlib_cli_output (arg, "%U", format_wg_peer, peeri); + + return (WALK_CONTINUE); +} + +static clib_error_t * +wg_show_peer_command_fn (vlib_main_t * vm, + unformat_input_t * input, vlib_cli_command_t * cmd) +{ + wg_peer_walk (wg_peer_show_one, vm); + + return NULL; +} + +/* *INDENT-OFF* */ +VLIB_CLI_COMMAND (wg_show_peers_command, static) = +{ + .path = "show wireguard peer", + .short_help = "show wireguard peer", + .function = wg_show_peer_command_fn, +}; +/* *INDENT-ON* */ + +static walk_rc_t +wg_if_show_one (index_t itfi, void *arg) +{ + vlib_cli_output (arg, "%U", format_wg_if, itfi); + + return (WALK_CONTINUE); +} + +static clib_error_t * +wg_show_if_command_fn (vlib_main_t * vm, + unformat_input_t * input, vlib_cli_command_t * cmd) +{ + wg_if_walk (wg_if_show_one, vm); + + return NULL; +} + +/* *INDENT-OFF* */ +VLIB_CLI_COMMAND (wg_show_itfs_command, static) = +{ + .path = "show wireguard interface", + .short_help = "show wireguard", + .function = wg_show_if_command_fn, +}; +/* *INDENT-ON* */ + +/* + * fd.io coding-style-patch-verification: ON + * + * Local Variables: + * eval: (c-set-style "gnu") + * End: + */ diff --git a/src/plugins/wireguard/wireguard_cookie.c b/src/plugins/wireguard/wireguard_cookie.c new file mode 100755 index 00000000000..aa476f792a1 --- /dev/null +++ b/src/plugins/wireguard/wireguard_cookie.c @@ -0,0 +1,169 @@ +/* + * Copyright (c) 2020 Doc.ai and/or its affiliates. + * Copyright (c) 2015-2020 Jason A. Donenfeld <Jason@zx2c4.com>. + * Copyright (c) 2019-2020 Matt Dunwoodie <ncon@noconroy.net>. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include <stddef.h> +#include <openssl/rand.h> +#include <vlib/vlib.h> + +#include <wireguard/wireguard_cookie.h> +#include <wireguard/wireguard.h> + +static void cookie_precompute_key (uint8_t *, + const uint8_t[COOKIE_INPUT_SIZE], + const char *); +static void cookie_macs_mac1 (message_macs_t *, const void *, size_t, + const uint8_t[COOKIE_KEY_SIZE]); +static void cookie_macs_mac2 (message_macs_t *, const void *, size_t, + const uint8_t[COOKIE_COOKIE_SIZE]); +static void cookie_checker_make_cookie (vlib_main_t * vm, cookie_checker_t *, + uint8_t[COOKIE_COOKIE_SIZE], + ip4_address_t ip4, u16 udp_port); + +/* Public Functions */ +void +cookie_maker_init (cookie_maker_t * cp, const uint8_t key[COOKIE_INPUT_SIZE]) +{ + clib_memset (cp, 0, sizeof (*cp)); + cookie_precompute_key (cp->cp_mac1_key, key, COOKIE_MAC1_KEY_LABEL); + cookie_precompute_key (cp->cp_cookie_key, key, COOKIE_COOKIE_KEY_LABEL); +} + +void +cookie_checker_update (cookie_checker_t * cc, uint8_t key[COOKIE_INPUT_SIZE]) +{ + if (key) + { + cookie_precompute_key (cc->cc_mac1_key, key, COOKIE_MAC1_KEY_LABEL); + cookie_precompute_key (cc->cc_cookie_key, key, COOKIE_COOKIE_KEY_LABEL); + } + else + { + clib_memset (cc->cc_mac1_key, 0, sizeof (cc->cc_mac1_key)); + clib_memset (cc->cc_cookie_key, 0, sizeof (cc->cc_cookie_key)); + } +} + +void +cookie_maker_mac (cookie_maker_t * cp, message_macs_t * cm, void *buf, + size_t len) +{ + len = len - sizeof (message_macs_t); + cookie_macs_mac1 (cm, buf, len, cp->cp_mac1_key); + + clib_memcpy (cp->cp_mac1_last, cm->mac1, COOKIE_MAC_SIZE); + cp->cp_mac1_valid = 1; + + if (!wg_birthdate_has_expired (cp->cp_birthdate, + COOKIE_SECRET_MAX_AGE - + COOKIE_SECRET_LATENCY)) + cookie_macs_mac2 (cm, buf, len, cp->cp_cookie); + else + clib_memset (cm->mac2, 0, COOKIE_MAC_SIZE); +} + +enum cookie_mac_state +cookie_checker_validate_macs (vlib_main_t * vm, cookie_checker_t * cc, + message_macs_t * cm, void *buf, size_t len, + bool busy, ip4_address_t ip4, u16 udp_port) +{ + message_macs_t our_cm; + uint8_t cookie[COOKIE_COOKIE_SIZE]; + + len = len - sizeof (message_macs_t); + cookie_macs_mac1 (&our_cm, buf, len, cc->cc_mac1_key); + + /* If mac1 is invald, we want to drop the packet */ + if (clib_memcmp (our_cm.mac1, cm->mac1, COOKIE_MAC_SIZE) != 0) + return INVALID_MAC; + + if (!busy) + return VALID_MAC_BUT_NO_COOKIE; + + cookie_checker_make_cookie (vm, cc, cookie, ip4, udp_port); + cookie_macs_mac2 (&our_cm, buf, len, cookie); + + /* If the mac2 is invalid, we want to send a cookie response */ + if (clib_memcmp (our_cm.mac2, cm->mac2, COOKIE_MAC_SIZE) != 0) + return VALID_MAC_BUT_NO_COOKIE; + + return VALID_MAC_WITH_COOKIE; +} + +/* Private functions */ +static void +cookie_precompute_key (uint8_t * key, const uint8_t input[COOKIE_INPUT_SIZE], + const char *label) +{ + blake2s_state_t blake; + + blake2s_init (&blake, COOKIE_KEY_SIZE); + blake2s_update (&blake, (const uint8_t *) label, strlen (label)); + blake2s_update (&blake, input, COOKIE_INPUT_SIZE); + blake2s_final (&blake, key, COOKIE_KEY_SIZE); +} + +static void +cookie_macs_mac1 (message_macs_t * cm, const void *buf, size_t len, + const uint8_t key[COOKIE_KEY_SIZE]) +{ + blake2s_state_t state; + blake2s_init_key (&state, COOKIE_MAC_SIZE, key, COOKIE_KEY_SIZE); + blake2s_update (&state, buf, len); + blake2s_final (&state, cm->mac1, COOKIE_MAC_SIZE); + +} + +static void +cookie_macs_mac2 (message_macs_t * cm, const void *buf, size_t len, + const uint8_t key[COOKIE_COOKIE_SIZE]) +{ + blake2s_state_t state; + blake2s_init_key (&state, COOKIE_MAC_SIZE, key, COOKIE_COOKIE_SIZE); + blake2s_update (&state, buf, len); + blake2s_update (&state, cm->mac1, COOKIE_MAC_SIZE); + blake2s_final (&state, cm->mac2, COOKIE_MAC_SIZE); +} + +static void +cookie_checker_make_cookie (vlib_main_t * vm, cookie_checker_t * cc, + uint8_t cookie[COOKIE_COOKIE_SIZE], + ip4_address_t ip4, u16 udp_port) +{ + blake2s_state_t state; + + if (wg_birthdate_has_expired (cc->cc_secret_birthdate, + COOKIE_SECRET_MAX_AGE)) + { + cc->cc_secret_birthdate = vlib_time_now (vm); + RAND_bytes (cc->cc_secret, COOKIE_SECRET_SIZE); + } + + blake2s_init_key (&state, COOKIE_COOKIE_SIZE, cc->cc_secret, + COOKIE_SECRET_SIZE); + + blake2s_update (&state, ip4.as_u8, sizeof (ip4_address_t)); //TODO: IP6 + blake2s_update (&state, (u8 *) & udp_port, sizeof (u16)); + blake2s_final (&state, cookie, COOKIE_COOKIE_SIZE); +} + +/* + * fd.io coding-style-patch-verification: ON + * + * Local Variables: + * eval: (c-set-style "gnu") + * End: + */ diff --git a/src/plugins/wireguard/wireguard_cookie.h b/src/plugins/wireguard/wireguard_cookie.h new file mode 100755 index 00000000000..489cce81325 --- /dev/null +++ b/src/plugins/wireguard/wireguard_cookie.h @@ -0,0 +1,101 @@ +/* + * Copyright (c) 2020 Doc.ai and/or its affiliates. + * Copyright (c) 2015-2020 Jason A. Donenfeld <Jason@zx2c4.com>. + * Copyright (c) 2019-2020 Matt Dunwoodie <ncon@noconroy.net>. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef __included_wg_cookie_h__ +#define __included_wg_cookie_h__ + +#include <vnet/ip/ip4_packet.h> +#include <wireguard/wireguard_noise.h> + +enum cookie_mac_state +{ + INVALID_MAC, + VALID_MAC_BUT_NO_COOKIE, + VALID_MAC_WITH_COOKIE +}; + +#define COOKIE_MAC_SIZE 16 +#define COOKIE_KEY_SIZE 32 +#define COOKIE_NONCE_SIZE 24 +#define COOKIE_COOKIE_SIZE 16 +#define COOKIE_SECRET_SIZE 32 +#define COOKIE_INPUT_SIZE 32 +#define COOKIE_ENCRYPTED_SIZE (COOKIE_COOKIE_SIZE + COOKIE_MAC_SIZE) + +#define COOKIE_MAC1_KEY_LABEL "mac1----" +#define COOKIE_COOKIE_KEY_LABEL "cookie--" +#define COOKIE_SECRET_MAX_AGE 120 +#define COOKIE_SECRET_LATENCY 5 + +/* Constants for initiation rate limiting */ +#define RATELIMIT_SIZE (1 << 13) +#define RATELIMIT_SIZE_MAX (RATELIMIT_SIZE * 8) +#define NSEC_PER_SEC 1000000000LL +#define INITIATIONS_PER_SECOND 20 +#define INITIATIONS_BURSTABLE 5 +#define INITIATION_COST (NSEC_PER_SEC / INITIATIONS_PER_SECOND) +#define TOKEN_MAX (INITIATION_COST * INITIATIONS_BURSTABLE) +#define ELEMENT_TIMEOUT 1 +#define IPV4_MASK_SIZE 4 /* Use all 4 bytes of IPv4 address */ +#define IPV6_MASK_SIZE 8 /* Use top 8 bytes (/64) of IPv6 address */ + +typedef struct cookie_macs +{ + uint8_t mac1[COOKIE_MAC_SIZE]; + uint8_t mac2[COOKIE_MAC_SIZE]; +} message_macs_t; + +typedef struct cookie_maker +{ + uint8_t cp_mac1_key[COOKIE_KEY_SIZE]; + uint8_t cp_cookie_key[COOKIE_KEY_SIZE]; + + uint8_t cp_cookie[COOKIE_COOKIE_SIZE]; + f64 cp_birthdate; + int cp_mac1_valid; + uint8_t cp_mac1_last[COOKIE_MAC_SIZE]; +} cookie_maker_t; + +typedef struct cookie_checker +{ + uint8_t cc_mac1_key[COOKIE_KEY_SIZE]; + uint8_t cc_cookie_key[COOKIE_KEY_SIZE]; + + f64 cc_secret_birthdate; + uint8_t cc_secret[COOKIE_SECRET_SIZE]; +} cookie_checker_t; + + +void cookie_maker_init (cookie_maker_t *, const uint8_t[COOKIE_INPUT_SIZE]); +void cookie_checker_update (cookie_checker_t *, uint8_t[COOKIE_INPUT_SIZE]); +void cookie_maker_mac (cookie_maker_t *, message_macs_t *, void *, size_t); +enum cookie_mac_state cookie_checker_validate_macs (vlib_main_t * vm, + cookie_checker_t *, + message_macs_t *, void *, + size_t, bool, + ip4_address_t ip4, + u16 udp_port); + +#endif /* __included_wg_cookie_h__ */ + +/* + * fd.io coding-style-patch-verification: ON + * + * Local Variables: + * eval: (c-set-style "gnu") + * End: + */ diff --git a/src/plugins/wireguard/wireguard_if.c b/src/plugins/wireguard/wireguard_if.c new file mode 100644 index 00000000000..ff8ed35477e --- /dev/null +++ b/src/plugins/wireguard/wireguard_if.c @@ -0,0 +1,422 @@ + +#include <vnet/adj/adj_midchain.h> +#include <vnet/udp/udp.h> + +#include <wireguard/wireguard_messages.h> +#include <wireguard/wireguard_if.h> +#include <wireguard/wireguard.h> + +/* pool of interfaces */ +wg_if_t *wg_if_pool; + +/* bitmap of Allocated WG_ITF instances */ +static uword *wg_if_instances; + +/* vector of interfaces key'd on their sw_if_index */ +static index_t *wg_if_index_by_sw_if_index; + +/* vector of interfaces key'd on their UDP port (in network order) */ +index_t *wg_if_index_by_port; + +static u8 * +format_wg_if_name (u8 * s, va_list * args) +{ + u32 dev_instance = va_arg (*args, u32); + return format (s, "wg%d", dev_instance); +} + +u8 * +format_wg_if (u8 * s, va_list * args) +{ + index_t wgii = va_arg (*args, u32); + wg_if_t *wgi = wg_if_get (wgii); + u8 key[NOISE_KEY_LEN_BASE64]; + + key_to_base64 (wgi->local.l_private, NOISE_PUBLIC_KEY_LEN, key); + + s = format (s, "[%d] %U src:%U port:%d", + wgii, + format_vnet_sw_if_index_name, vnet_get_main (), + wgi->sw_if_index, format_ip_address, &wgi->src_ip, wgi->port); + + key_to_base64 (wgi->local.l_private, NOISE_PUBLIC_KEY_LEN, key); + + s = format (s, " private-key:%s", key); + + key_to_base64 (wgi->local.l_public, NOISE_PUBLIC_KEY_LEN, key); + + s = format (s, " public-key:%s", key); + + return (s); +} + +index_t +wg_if_find_by_sw_if_index (u32 sw_if_index) +{ + if (vec_len (wg_if_index_by_sw_if_index) <= sw_if_index) + return INDEX_INVALID; + u32 ti = wg_if_index_by_sw_if_index[sw_if_index]; + if (ti == ~0) + return INDEX_INVALID; + + return (ti); +} + +static noise_remote_t * +wg_remote_get (uint8_t public[NOISE_PUBLIC_KEY_LEN]) +{ + wg_main_t *wmp = &wg_main; + wg_peer_t *peer = NULL; + wg_peer_t *peer_iter; + /* *INDENT-OFF* */ + pool_foreach (peer_iter, wmp->peers, + ({ + if (!memcmp (peer_iter->remote.r_public, public, NOISE_PUBLIC_KEY_LEN)) + { + peer = peer_iter; + break; + } + })); + /* *INDENT-ON* */ + return peer ? &peer->remote : NULL; +} + +static uint32_t +wg_index_set (noise_remote_t * remote) +{ + wg_main_t *wmp = &wg_main; + u32 rnd_seed = (u32) (vlib_time_now (wmp->vlib_main) * 1e6); + u32 ret = + wg_index_table_add (&wmp->index_table, remote->r_peer_idx, rnd_seed); + return ret; +} + +static void +wg_index_drop (uint32_t key) +{ + wg_main_t *wmp = &wg_main; + wg_index_table_del (&wmp->index_table, key); +} + +static clib_error_t * +wg_if_admin_up_down (vnet_main_t * vnm, u32 hw_if_index, u32 flags) +{ + vnet_hw_interface_t *hi; + index_t wgii; + u32 hw_flags; + + hi = vnet_get_hw_interface (vnm, hw_if_index); + hw_flags = (flags & VNET_SW_INTERFACE_FLAG_ADMIN_UP ? + VNET_HW_INTERFACE_FLAG_LINK_UP : 0); + vnet_hw_interface_set_flags (vnm, hw_if_index, hw_flags); + + wgii = wg_if_find_by_sw_if_index (hi->sw_if_index); + + wg_if_peer_walk (wg_if_get (wgii), wg_peer_if_admin_state_change, NULL); + + return (NULL); +} + +void +wg_if_update_adj (vnet_main_t * vnm, u32 sw_if_index, adj_index_t ai) +{ + /* The peers manage the adjacencies */ +} + + +/* *INDENT-OFF* */ +VNET_DEVICE_CLASS (wg_if_device_class) = { + .name = "Wireguard Tunnel", + .format_device_name = format_wg_if_name, + .admin_up_down_function = wg_if_admin_up_down, +}; + +VNET_HW_INTERFACE_CLASS(wg_hw_interface_class) = { + .name = "Wireguard", + .update_adjacency = wg_if_update_adj, + .flags = VNET_HW_INTERFACE_CLASS_FLAG_NBMA, +}; +/* *INDENT-ON* */ + +/* + * Maintain a bitmap of allocated wg_if instance numbers. + */ +#define WG_ITF_MAX_INSTANCE (16 * 1024) + +static u32 +wg_if_instance_alloc (u32 want) +{ + /* + * Check for dynamically allocated instance number. + */ + if (~0 == want) + { + u32 bit; + + bit = clib_bitmap_first_clear (wg_if_instances); + if (bit >= WG_ITF_MAX_INSTANCE) + { + return ~0; + } + wg_if_instances = clib_bitmap_set (wg_if_instances, bit, 1); + return bit; + } + + /* + * In range? + */ + if (want >= WG_ITF_MAX_INSTANCE) + { + return ~0; + } + + /* + * Already in use? + */ + if (clib_bitmap_get (wg_if_instances, want)) + { + return ~0; + } + + /* + * Grant allocation request. + */ + wg_if_instances = clib_bitmap_set (wg_if_instances, want, 1); + + return want; +} + +static int +wg_if_instance_free (u32 instance) +{ + if (instance >= WG_ITF_MAX_INSTANCE) + { + return -1; + } + + if (clib_bitmap_get (wg_if_instances, instance) == 0) + { + return -1; + } + + wg_if_instances = clib_bitmap_set (wg_if_instances, instance, 0); + return 0; +} + + +int +wg_if_create (u32 user_instance, + const u8 private_key[NOISE_PUBLIC_KEY_LEN], + u16 port, const ip_address_t * src_ip, u32 * sw_if_indexp) +{ + vnet_main_t *vnm = vnet_get_main (); + u32 instance, hw_if_index; + vnet_hw_interface_t *hi; + wg_if_t *wg_if; + + ASSERT (sw_if_indexp); + + *sw_if_indexp = (u32) ~ 0; + + /* + * Allocate a wg_if instance. Either select on dynamically + * or try to use the desired user_instance number. + */ + instance = wg_if_instance_alloc (user_instance); + if (instance == ~0) + return VNET_API_ERROR_INVALID_REGISTRATION; + + pool_get (wg_if_pool, wg_if); + + /* tunnel index (or instance) */ + u32 t_idx = wg_if - wg_if_pool; + + wg_if->user_instance = instance; + if (~0 == wg_if->user_instance) + wg_if->user_instance = t_idx; + + udp_dst_port_info_t *pi = udp_get_dst_port_info (&udp_main, port, UDP_IP4); + if (pi) + return (VNET_API_ERROR_VALUE_EXIST); + udp_register_dst_port (vlib_get_main (), port, wg_input_node.index, 1); + + vec_validate_init_empty (wg_if_index_by_port, port, INDEX_INVALID); + wg_if_index_by_port[port] = wg_if - wg_if_pool; + + wg_if->port = port; + struct noise_upcall upcall; + upcall.u_remote_get = wg_remote_get; + upcall.u_index_set = wg_index_set; + upcall.u_index_drop = wg_index_drop; + + noise_local_init (&wg_if->local, &upcall); + noise_local_set_private (&wg_if->local, private_key); + cookie_checker_update (&wg_if->cookie_checker, wg_if->local.l_public); + + hw_if_index = vnet_register_interface (vnm, + wg_if_device_class.index, + t_idx, + wg_hw_interface_class.index, t_idx); + + hi = vnet_get_hw_interface (vnm, hw_if_index); + + vec_validate_init_empty (wg_if_index_by_sw_if_index, hi->sw_if_index, + INDEX_INVALID); + wg_if_index_by_sw_if_index[hi->sw_if_index] = t_idx; + + ip_address_copy (&wg_if->src_ip, src_ip); + wg_if->sw_if_index = *sw_if_indexp = hi->sw_if_index; + + return 0; +} + +int +wg_if_delete (u32 sw_if_index) +{ + vnet_main_t *vnm = vnet_get_main (); + + if (pool_is_free_index (vnm->interface_main.sw_interfaces, sw_if_index)) + return VNET_API_ERROR_INVALID_SW_IF_INDEX; + + vnet_hw_interface_t *hw = vnet_get_sup_hw_interface (vnm, sw_if_index); + if (hw == 0 || hw->dev_class_index != wg_if_device_class.index) + return VNET_API_ERROR_INVALID_SW_IF_INDEX; + + wg_if_t *wg_if; + wg_if = wg_if_get (wg_if_find_by_sw_if_index (sw_if_index)); + if (NULL == wg_if) + return VNET_API_ERROR_INVALID_SW_IF_INDEX; + + if (wg_if_instance_free (hw->dev_instance) < 0) + return VNET_API_ERROR_INVALID_SW_IF_INDEX; + + wg_if_index_by_port[wg_if->port] = INDEX_INVALID; + vnet_delete_hw_interface (vnm, hw->hw_if_index); + pool_put (wg_if_pool, wg_if); + + return 0; +} + +void +wg_if_peer_add (wg_if_t * wgi, index_t peeri) +{ + hash_set (wgi->peers, peeri, peeri); + + if (1 == hash_elts (wgi->peers)) + vnet_feature_enable_disable ("ip4-output", "wg-output-tun", + wgi->sw_if_index, 1, 0, 0); +} + +void +wg_if_peer_remove (wg_if_t * wgi, index_t peeri) +{ + hash_unset (wgi->peers, peeri); + + if (0 == hash_elts (wgi->peers)) + vnet_feature_enable_disable ("ip4-output", "wg-output-tun", + wgi->sw_if_index, 0, 0, 0); +} + +void +wg_if_walk (wg_if_walk_cb_t fn, void *data) +{ + index_t wgii; + + /* *INDENT-OFF* */ + pool_foreach_index (wgii, wg_if_pool, + { + if (WALK_STOP == fn(wgii, data)) + break; + }); + /* *INDENT-ON* */ +} + +void +wg_if_peer_walk (wg_if_t * wgi, wg_if_peer_walk_cb_t fn, void *data) +{ + index_t peeri, val; + + /* *INDENT-OFF* */ + hash_foreach (peeri, val, wgi->peers, + { + if (WALK_STOP == fn(wgi, peeri, data)) + break; + }); + /* *INDENT-ON* */ +} + + +static void +wg_if_table_bind_v4 (ip4_main_t * im, + uword opaque, + u32 sw_if_index, u32 new_fib_index, u32 old_fib_index) +{ + wg_if_t *wg_if; + + wg_if = wg_if_get (wg_if_find_by_sw_if_index (sw_if_index)); + if (NULL == wg_if) + return; + + wg_peer_table_bind_ctx_t ctx = { + .af = AF_IP4, + .old_fib_index = old_fib_index, + .new_fib_index = new_fib_index, + }; + + wg_if_peer_walk (wg_if, wg_peer_if_table_change, &ctx); +} + +static void +wg_if_table_bind_v6 (ip6_main_t * im, + uword opaque, + u32 sw_if_index, u32 new_fib_index, u32 old_fib_index) +{ + wg_if_t *wg_if; + + wg_if = wg_if_get (wg_if_find_by_sw_if_index (sw_if_index)); + if (NULL == wg_if) + return; + + wg_peer_table_bind_ctx_t ctx = { + .af = AF_IP6, + .old_fib_index = old_fib_index, + .new_fib_index = new_fib_index, + }; + + wg_if_peer_walk (wg_if, wg_peer_if_table_change, &ctx); +} + +static clib_error_t * +wg_if_module_init (vlib_main_t * vm) +{ + { + ip4_table_bind_callback_t cb = { + .function = wg_if_table_bind_v4, + }; + vec_add1 (ip4_main.table_bind_callbacks, cb); + } + { + ip6_table_bind_callback_t cb = { + .function = wg_if_table_bind_v6, + }; + vec_add1 (ip6_main.table_bind_callbacks, cb); + } + + return (NULL); +} + +/* *INDENT-OFF* */ +VLIB_INIT_FUNCTION (wg_if_module_init) = +{ + .runs_after = VLIB_INITS("ip_main_init"), +}; +/* *INDENT-ON* */ + + +/* + * fd.io coding-style-patch-verification: ON + * + * Local Variables: + * eval: (c-set-style "gnu") + * End: + */ diff --git a/src/plugins/wireguard/wireguard_if.h b/src/plugins/wireguard/wireguard_if.h new file mode 100644 index 00000000000..9e6b6190e0e --- /dev/null +++ b/src/plugins/wireguard/wireguard_if.h @@ -0,0 +1,94 @@ +/* + * Copyright (c) 2020 Doc.ai and/or its affiliates. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef __WG_ITF_H__ +#define __WG_ITF_H__ + +#include <wireguard/wireguard_index_table.h> +#include <wireguard/wireguard_messages.h> + +typedef struct wg_if_t_ +{ + int user_instance; + u32 sw_if_index; + + // Interface params + noise_local_t local; + cookie_checker_t cookie_checker; + u16 port; + + wg_index_table_t index_table; + + /* Source IP address for originated packets */ + ip_address_t src_ip; + + /* hash table of peers on this link */ + uword *peers; +} wg_if_t; + + +int wg_if_create (u32 user_instance, + const u8 private_key_64[NOISE_PUBLIC_KEY_LEN], + u16 port, const ip_address_t * src_ip, u32 * sw_if_indexp); +int wg_if_delete (u32 sw_if_index); +index_t wg_if_find_by_sw_if_index (u32 sw_if_index); + +u8 *format_wg_if (u8 * s, va_list * va); + +typedef walk_rc_t (*wg_if_walk_cb_t) (index_t wgi, void *data); +void wg_if_walk (wg_if_walk_cb_t fn, void *data); + +typedef walk_rc_t (*wg_if_peer_walk_cb_t) (wg_if_t * wgi, index_t peeri, + void *data); +void wg_if_peer_walk (wg_if_t * wgi, wg_if_peer_walk_cb_t fn, void *data); + +void wg_if_peer_add (wg_if_t * wgi, index_t peeri); +void wg_if_peer_remove (wg_if_t * wgi, index_t peeri); + +/** + * Data-plane exposed functions + */ +extern wg_if_t *wg_if_pool; + +static_always_inline wg_if_t * +wg_if_get (index_t wgii) +{ + if (INDEX_INVALID == wgii) + return (NULL); + return (pool_elt_at_index (wg_if_pool, wgii)); +} + +extern index_t *wg_if_index_by_port; + +static_always_inline wg_if_t * +wg_if_get_by_port (u16 port) +{ + if (vec_len (wg_if_index_by_port) < port) + return (NULL); + if (INDEX_INVALID == wg_if_index_by_port[port]) + return (NULL); + return (wg_if_get (wg_if_index_by_port[port])); +} + + +#endif + +/* + * fd.io coding-style-patch-verification: ON + * + * Local Variables: + * eval: (c-set-style "gnu") + * End: + */ diff --git a/src/plugins/wireguard/wireguard_index_table.c b/src/plugins/wireguard/wireguard_index_table.c new file mode 100755 index 00000000000..5f81204b4c0 --- /dev/null +++ b/src/plugins/wireguard/wireguard_index_table.c @@ -0,0 +1,62 @@ +/* + * Copyright (c) 2020 Doc.ai and/or its affiliates. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include <vppinfra/hash.h> +#include <vppinfra/pool.h> +#include <vppinfra/random.h> +#include <wireguard/wireguard_index_table.h> + +u32 +wg_index_table_add (wg_index_table_t * table, u32 peer_pool_idx, u32 rnd_seed) +{ + u32 key; + + while (1) + { + key = random_u32 (&rnd_seed); + if (hash_get (table->hash, key)) + continue; + + hash_set (table->hash, key, peer_pool_idx); + break; + } + return key; +} + +void +wg_index_table_del (wg_index_table_t * table, u32 key) +{ + uword *p; + p = hash_get (table->hash, key); + if (p) + hash_unset (table->hash, key); +} + +u32 * +wg_index_table_lookup (const wg_index_table_t * table, u32 key) +{ + uword *p; + + p = hash_get (table->hash, key); + return (u32 *) p; +} + +/* + * fd.io coding-style-patch-verification: ON + * + * Local Variables: + * eval: (c-set-style "gnu") + * End: + */ diff --git a/src/plugins/wireguard/wireguard_index_table.h b/src/plugins/wireguard/wireguard_index_table.h new file mode 100755 index 00000000000..67cae1f49d5 --- /dev/null +++ b/src/plugins/wireguard/wireguard_index_table.h @@ -0,0 +1,39 @@ +/* + * Copyright (c) 2020 Doc.ai and/or its affiliates. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef __included_wg_index_table_h__ +#define __included_wg_index_table_h__ + +#include <vppinfra/types.h> + +typedef struct +{ + uword *hash; +} wg_index_table_t; + +u32 wg_index_table_add (wg_index_table_t * table, u32 peer_pool_idx, + u32 rnd_seed); +void wg_index_table_del (wg_index_table_t * table, u32 key); +u32 *wg_index_table_lookup (const wg_index_table_t * table, u32 key); + +#endif //__included_wg_index_table_h__ + +/* + * fd.io coding-style-patch-verification: ON + * + * Local Variables: + * eval: (c-set-style "gnu") + * End: + */ diff --git a/src/plugins/wireguard/wireguard_input.c b/src/plugins/wireguard/wireguard_input.c new file mode 100755 index 00000000000..832ad031daa --- /dev/null +++ b/src/plugins/wireguard/wireguard_input.c @@ -0,0 +1,451 @@ +/* + * Copyright (c) 2020 Doc.ai and/or its affiliates. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include <vlib/vlib.h> +#include <vnet/vnet.h> +#include <vnet/pg/pg.h> +#include <vppinfra/error.h> +#include <wireguard/wireguard.h> + +#include <wireguard/wireguard_send.h> +#include <wireguard/wireguard_if.h> + +#define foreach_wg_input_error \ + _(NONE, "No error") \ + _(HANDSHAKE_MAC, "Invalid MAC handshake") \ + _(PEER, "Peer error") \ + _(INTERFACE, "Interface error") \ + _(DECRYPTION, "Failed during decryption") \ + _(KEEPALIVE_SEND, "Failed while sending Keepalive") \ + _(HANDSHAKE_SEND, "Failed while sending Handshake") \ + _(UNDEFINED, "Undefined error") + +typedef enum +{ +#define _(sym,str) WG_INPUT_ERROR_##sym, + foreach_wg_input_error +#undef _ + WG_INPUT_N_ERROR, +} wg_input_error_t; + +static char *wg_input_error_strings[] = { +#define _(sym,string) string, + foreach_wg_input_error +#undef _ +}; + +typedef struct +{ + message_type_t type; + u16 current_length; + bool is_keepalive; + +} wg_input_trace_t; + +u8 * +format_wg_message_type (u8 * s, va_list * args) +{ + message_type_t type = va_arg (*args, message_type_t); + + switch (type) + { +#define _(v,a) case MESSAGE_##v: return (format (s, "%s", a)); + foreach_wg_message_type +#undef _ + } + return (format (s, "unknown")); +} + +/* packet trace format function */ +static u8 * +format_wg_input_trace (u8 * s, va_list * args) +{ + CLIB_UNUSED (vlib_main_t * vm) = va_arg (*args, vlib_main_t *); + CLIB_UNUSED (vlib_node_t * node) = va_arg (*args, vlib_node_t *); + + wg_input_trace_t *t = va_arg (*args, wg_input_trace_t *); + + s = format (s, "WG input: \n"); + s = format (s, " Type: %U\n", format_wg_message_type, t->type); + s = format (s, " Length: %d\n", t->current_length); + s = format (s, " Keepalive: %s", t->is_keepalive ? "true" : "false"); + + return s; +} + +typedef enum +{ + WG_INPUT_NEXT_IP4_INPUT, + WG_INPUT_NEXT_PUNT, + WG_INPUT_NEXT_ERROR, + WG_INPUT_N_NEXT, +} wg_input_next_t; + +/* static void */ +/* set_peer_address (wg_peer_t * peer, ip4_address_t ip4, u16 udp_port) */ +/* { */ +/* if (peer) */ +/* { */ +/* ip46_address_set_ip4 (&peer->dst.addr, &ip4); */ +/* peer->dst.port = udp_port; */ +/* } */ +/* } */ + +static wg_input_error_t +wg_handshake_process (vlib_main_t * vm, wg_main_t * wmp, vlib_buffer_t * b) +{ + enum cookie_mac_state mac_state; + bool packet_needs_cookie; + bool under_load; + wg_if_t *wg_if; + wg_peer_t *peer = NULL; + + void *current_b_data = vlib_buffer_get_current (b); + + udp_header_t *uhd = current_b_data - sizeof (udp_header_t); + ip4_header_t *iph = + current_b_data - sizeof (udp_header_t) - sizeof (ip4_header_t); + ip4_address_t ip4_src = iph->src_address; + u16 udp_src_port = clib_host_to_net_u16 (uhd->src_port);; + u16 udp_dst_port = clib_host_to_net_u16 (uhd->dst_port);; + + message_header_t *header = current_b_data; + under_load = false; + + wg_if = wg_if_get_by_port (udp_dst_port); + + if (NULL == wg_if) + return WG_INPUT_ERROR_INTERFACE; + + if (header->type == MESSAGE_HANDSHAKE_COOKIE) + { + message_handshake_cookie_t *packet = + (message_handshake_cookie_t *) current_b_data; + u32 *entry = + wg_index_table_lookup (&wmp->index_table, packet->receiver_index); + if (entry) + { + peer = pool_elt_at_index (wmp->peers, *entry); + } + if (!peer) + return WG_INPUT_ERROR_PEER; + + // TODO: Implement cookie_maker_consume_payload + + return WG_INPUT_ERROR_NONE; + } + + u32 len = (header->type == MESSAGE_HANDSHAKE_INITIATION ? + sizeof (message_handshake_initiation_t) : + sizeof (message_handshake_response_t)); + + message_macs_t *macs = (message_macs_t *) + ((u8 *) current_b_data + len - sizeof (*macs)); + + mac_state = + cookie_checker_validate_macs (vm, &wg_if->cookie_checker, macs, + current_b_data, len, under_load, ip4_src, + udp_src_port); + + if ((under_load && mac_state == VALID_MAC_WITH_COOKIE) + || (!under_load && mac_state == VALID_MAC_BUT_NO_COOKIE)) + packet_needs_cookie = false; + else if (under_load && mac_state == VALID_MAC_BUT_NO_COOKIE) + packet_needs_cookie = true; + else + return WG_INPUT_ERROR_HANDSHAKE_MAC; + + switch (header->type) + { + case MESSAGE_HANDSHAKE_INITIATION: + { + message_handshake_initiation_t *message = current_b_data; + + if (packet_needs_cookie) + { + // TODO: Add processing + } + noise_remote_t *rp; + + if (noise_consume_initiation + (wmp->vlib_main, &wg_if->local, &rp, message->sender_index, + message->unencrypted_ephemeral, message->encrypted_static, + message->encrypted_timestamp)) + { + peer = pool_elt_at_index (wmp->peers, rp->r_peer_idx); + } + + if (!peer) + return WG_INPUT_ERROR_PEER; + + // set_peer_address (peer, ip4_src, udp_src_port); + if (PREDICT_FALSE (!wg_send_handshake_response (vm, peer))) + { + vlib_node_increment_counter (vm, wg_input_node.index, + WG_INPUT_ERROR_HANDSHAKE_SEND, 1); + } + break; + } + case MESSAGE_HANDSHAKE_RESPONSE: + { + message_handshake_response_t *resp = current_b_data; + u32 *entry = + wg_index_table_lookup (&wmp->index_table, resp->receiver_index); + if (entry) + { + peer = pool_elt_at_index (wmp->peers, *entry); + if (!peer || peer->is_dead) + return WG_INPUT_ERROR_PEER; + } + + if (!noise_consume_response + (wmp->vlib_main, &peer->remote, resp->sender_index, + resp->receiver_index, resp->unencrypted_ephemeral, + resp->encrypted_nothing)) + { + return WG_INPUT_ERROR_PEER; + } + if (packet_needs_cookie) + { + // TODO: Add processing + } + + // set_peer_address (peer, ip4_src, udp_src_port); + if (noise_remote_begin_session (wmp->vlib_main, &peer->remote)) + { + wg_timers_session_derived (peer); + wg_timers_handshake_complete (peer); + if (PREDICT_FALSE (!wg_send_keepalive (vm, peer))) + { + vlib_node_increment_counter (vm, wg_input_node.index, + WG_INPUT_ERROR_KEEPALIVE_SEND, + 1); + } + } + break; + } + default: + break; + } + + wg_timers_any_authenticated_packet_received (peer); + wg_timers_any_authenticated_packet_traversal (peer); + return WG_INPUT_ERROR_NONE; +} + +static_always_inline bool +fib_prefix_is_cover_addr_4 (const fib_prefix_t * p1, + const ip4_address_t * ip4) +{ + switch (p1->fp_proto) + { + case FIB_PROTOCOL_IP4: + return (ip4_destination_matches_route (&ip4_main, + &p1->fp_addr.ip4, + ip4, p1->fp_len) != 0); + case FIB_PROTOCOL_IP6: + return (false); + case FIB_PROTOCOL_MPLS: + break; + } + return (false); +} + +VLIB_NODE_FN (wg_input_node) (vlib_main_t * vm, + vlib_node_runtime_t * node, + vlib_frame_t * frame) +{ + message_type_t header_type; + u32 n_left_from; + u32 *from; + vlib_buffer_t *bufs[VLIB_FRAME_SIZE], **b; + u16 nexts[VLIB_FRAME_SIZE], *next; + + from = vlib_frame_vector_args (frame); + n_left_from = frame->n_vectors; + b = bufs; + next = nexts; + + vlib_get_buffers (vm, from, bufs, n_left_from); + + wg_main_t *wmp = &wg_main; + wg_peer_t *peer = NULL; + + while (n_left_from > 0) + { + bool is_keepalive = false; + next[0] = WG_INPUT_NEXT_PUNT; + header_type = + ((message_header_t *) vlib_buffer_get_current (b[0]))->type; + + switch (header_type) + { + case MESSAGE_HANDSHAKE_INITIATION: + case MESSAGE_HANDSHAKE_RESPONSE: + case MESSAGE_HANDSHAKE_COOKIE: + { + wg_input_error_t ret = wg_handshake_process (vm, wmp, b[0]); + if (ret != WG_INPUT_ERROR_NONE) + { + next[0] = WG_INPUT_NEXT_ERROR; + b[0]->error = node->errors[ret]; + } + break; + } + case MESSAGE_DATA: + { + message_data_t *data = vlib_buffer_get_current (b[0]); + u32 *entry = + wg_index_table_lookup (&wmp->index_table, data->receiver_index); + + if (entry) + { + peer = pool_elt_at_index (wmp->peers, *entry); + if (!peer) + { + next[0] = WG_INPUT_NEXT_ERROR; + b[0]->error = node->errors[WG_INPUT_ERROR_PEER]; + goto out; + } + } + + u16 encr_len = b[0]->current_length - sizeof (message_data_t); + u16 decr_len = encr_len - NOISE_AUTHTAG_LEN; + u8 *decr_data = clib_mem_alloc (decr_len); + + enum noise_state_crypt state_cr = + noise_remote_decrypt (wmp->vlib_main, + &peer->remote, + data->receiver_index, + data->counter, + data->encrypted_data, + encr_len, + decr_data); + + switch (state_cr) + { + case SC_OK: + break; + case SC_CONN_RESET: + wg_timers_handshake_complete (peer); + break; + case SC_KEEP_KEY_FRESH: + if (PREDICT_FALSE (!wg_send_handshake (vm, peer, false))) + { + vlib_node_increment_counter (vm, wg_input_node.index, + WG_INPUT_ERROR_HANDSHAKE_SEND, + 1); + } + break; + case SC_FAILED: + next[0] = WG_INPUT_NEXT_ERROR; + b[0]->error = node->errors[WG_INPUT_ERROR_DECRYPTION]; + goto out; + default: + break; + } + + clib_memcpy (vlib_buffer_get_current (b[0]), decr_data, decr_len); + b[0]->current_length = decr_len; + b[0]->flags &= ~VNET_BUFFER_F_OFFLOAD_UDP_CKSUM; + + clib_mem_free (decr_data); + + wg_timers_any_authenticated_packet_received (peer); + wg_timers_any_authenticated_packet_traversal (peer); + + if (decr_len == 0) + { + is_keepalive = true; + goto out; + } + + wg_timers_data_received (peer); + + ip4_header_t *iph = vlib_buffer_get_current (b[0]); + + const wg_peer_allowed_ip_t *allowed_ip; + bool allowed = false; + + /* + * we could make this into an ACL, but the expectation + * is that there aren't many allowed IPs and thus a linear + * walk is fater than an ACL + */ + vec_foreach (allowed_ip, peer->allowed_ips) + { + if (fib_prefix_is_cover_addr_4 (&allowed_ip->prefix, + &iph->src_address)) + { + allowed = true; + break; + } + } + if (allowed) + { + vnet_buffer (b[0])->sw_if_index[VLIB_RX] = + peer->wg_sw_if_index; + next[0] = WG_INPUT_NEXT_IP4_INPUT; + } + break; + } + default: + break; + } + + out: + if (PREDICT_FALSE ((node->flags & VLIB_NODE_FLAG_TRACE) + && (b[0]->flags & VLIB_BUFFER_IS_TRACED))) + { + wg_input_trace_t *t = vlib_add_trace (vm, node, b[0], sizeof (*t)); + t->type = header_type; + t->current_length = b[0]->current_length; + t->is_keepalive = is_keepalive; + } + n_left_from -= 1; + next += 1; + b += 1; + } + vlib_buffer_enqueue_to_next (vm, node, from, nexts, frame->n_vectors); + + return frame->n_vectors; +} + +/* *INDENT-OFF* */ +VLIB_REGISTER_NODE (wg_input_node) = +{ + .name = "wg-input", + .vector_size = sizeof (u32), + .format_trace = format_wg_input_trace, + .type = VLIB_NODE_TYPE_INTERNAL, + .n_errors = ARRAY_LEN (wg_input_error_strings), + .error_strings = wg_input_error_strings, + .n_next_nodes = WG_INPUT_N_NEXT, + /* edit / add dispositions here */ + .next_nodes = { + [WG_INPUT_NEXT_IP4_INPUT] = "ip4-input-no-checksum", + [WG_INPUT_NEXT_PUNT] = "error-punt", + [WG_INPUT_NEXT_ERROR] = "error-drop", + }, +}; +/* *INDENT-ON* */ + +/* + * fd.io coding-style-patch-verification: ON + * + * Local Variables: + * eval: (c-set-style "gnu") + * End: + */ diff --git a/src/plugins/wireguard/wireguard_key.c b/src/plugins/wireguard/wireguard_key.c new file mode 100755 index 00000000000..db8c4864492 --- /dev/null +++ b/src/plugins/wireguard/wireguard_key.c @@ -0,0 +1,126 @@ +/* + * Copyright (c) 2020 Doc.ai and/or its affiliates. + * Copyright (c) 2005-2011 Jouni Malinen <j@w1.fi>. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include <wireguard/wireguard_key.h> +#include <openssl/evp.h> + +bool +curve25519_gen_shared (u8 shared_key[CURVE25519_KEY_SIZE], + const u8 secret_key[CURVE25519_KEY_SIZE], + const u8 basepoint[CURVE25519_KEY_SIZE]) +{ + + bool ret; + EVP_PKEY_CTX *ctx; + size_t key_len; + + EVP_PKEY *peerkey = NULL; + EVP_PKEY *pkey = + EVP_PKEY_new_raw_private_key (EVP_PKEY_X25519, NULL, secret_key, + CURVE25519_KEY_SIZE); + + ret = true; + + ctx = EVP_PKEY_CTX_new (pkey, NULL); + if (EVP_PKEY_derive_init (ctx) <= 0) + { + ret = false; + goto out; + } + + peerkey = + EVP_PKEY_new_raw_public_key (EVP_PKEY_X25519, NULL, basepoint, + CURVE25519_KEY_SIZE); + if (EVP_PKEY_derive_set_peer (ctx, peerkey) <= 0) + { + ret = false; + goto out; + } + + key_len = CURVE25519_KEY_SIZE; + if (EVP_PKEY_derive (ctx, shared_key, &key_len) <= 0) + { + ret = false; + } + +out: + EVP_PKEY_CTX_free (ctx); + EVP_PKEY_free (pkey); + EVP_PKEY_free (peerkey); + return ret; +} + +bool +curve25519_gen_public (u8 public_key[CURVE25519_KEY_SIZE], + const u8 secret_key[CURVE25519_KEY_SIZE]) +{ + size_t pub_len; + EVP_PKEY *pkey = + EVP_PKEY_new_raw_private_key (EVP_PKEY_X25519, NULL, secret_key, + CURVE25519_KEY_SIZE); + pub_len = CURVE25519_KEY_SIZE; + if (!EVP_PKEY_get_raw_public_key (pkey, public_key, &pub_len)) + { + EVP_PKEY_free (pkey); + return false; + } + EVP_PKEY_free (pkey); + return true; +} + +bool +curve25519_gen_secret (u8 secret_key[CURVE25519_KEY_SIZE]) +{ + size_t secret_len; + EVP_PKEY *pkey = NULL; + EVP_PKEY_CTX *pctx = EVP_PKEY_CTX_new_id (EVP_PKEY_X25519, NULL); + EVP_PKEY_keygen_init (pctx); + EVP_PKEY_keygen (pctx, &pkey); + EVP_PKEY_CTX_free (pctx); + + secret_len = CURVE25519_KEY_SIZE; + if (!EVP_PKEY_get_raw_private_key (pkey, secret_key, &secret_len)) + { + EVP_PKEY_free (pkey); + return false; + } + EVP_PKEY_free (pkey); + return true; +} + +bool +key_to_base64 (const u8 * src, size_t src_len, u8 * out) +{ + if (!EVP_EncodeBlock (out, src, src_len)) + return false; + return true; +} + +bool +key_from_base64 (const u8 * src, size_t src_len, u8 * out) +{ + if (EVP_DecodeBlock (out, src, src_len - 1) <= 0) + return false; + return true; +} + +/* + * fd.io coding-style-patch-verification: ON + * + * Local Variables: + * eval: (c-set-style "gnu") + * End: + */ diff --git a/src/plugins/wireguard/wireguard_key.h b/src/plugins/wireguard/wireguard_key.h new file mode 100755 index 00000000000..6decca67205 --- /dev/null +++ b/src/plugins/wireguard/wireguard_key.h @@ -0,0 +1,46 @@ +/* + * Copyright (c) 2020 Doc.ai and/or its affiliates. + * Copyright (c) 2005 Jouni Malinen <j@w1.fi>. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef __included_wg_convert_h__ +#define __included_wg_convert_h__ + +#include <stdbool.h> +#include <vlib/vlib.h> + +enum curve25519_lengths +{ + CURVE25519_KEY_SIZE = 32 +}; + +bool curve25519_gen_shared (u8 shared_key[CURVE25519_KEY_SIZE], + const u8 secret_key[CURVE25519_KEY_SIZE], + const u8 basepoint[CURVE25519_KEY_SIZE]); +bool curve25519_gen_secret (u8 secret[CURVE25519_KEY_SIZE]); +bool curve25519_gen_public (u8 public_key[CURVE25519_KEY_SIZE], + const u8 secret_key[CURVE25519_KEY_SIZE]); + +bool key_to_base64 (const u8 * src, size_t src_len, u8 * out); +bool key_from_base64 (const u8 * src, size_t src_len, u8 * out); + +#endif /* __included_wg_convert_h__ */ + +/* + * fd.io coding-style-patch-verification: ON + * + * Local Variables: + * eval: (c-set-style "gnu") + * End: + */ diff --git a/src/plugins/wireguard/wireguard_messages.h b/src/plugins/wireguard/wireguard_messages.h new file mode 100755 index 00000000000..3587c5c8a45 --- /dev/null +++ b/src/plugins/wireguard/wireguard_messages.h @@ -0,0 +1,104 @@ +/* + * Copyright (c) 2020 Doc.ai and/or its affiliates. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef __included_wg_messages_h__ +#define __included_wg_messages_h__ + +#include <stdint.h> +#include <wireguard/wireguard_noise.h> +#include <wireguard/wireguard_cookie.h> + +#define WG_TICK 0.01 /**< WG tick period (s) */ +#define WHZ (u32) (1/WG_TICK) /**< WG tick frequency */ + +#define NOISE_KEY_LEN_BASE64 ((((NOISE_PUBLIC_KEY_LEN) + 2) / 3) * 4 + 1) +#define noise_encrypted_len(plain_len) ((plain_len) + NOISE_AUTHTAG_LEN) + +enum limits +{ + REKEY_TIMEOUT = 5, + REKEY_TIMEOUT_JITTER = WHZ / 3, + KEEPALIVE_TIMEOUT = 10, + MAX_TIMER_HANDSHAKES = 90 / REKEY_TIMEOUT, + MAX_PEERS = 1U << 20 +}; + +#define foreach_wg_message_type \ + _(INVALID, "Invalid") \ + _(HANDSHAKE_INITIATION, "Handshake initiation") \ + _(HANDSHAKE_RESPONSE, "Handshake response") \ + _(HANDSHAKE_COOKIE, "Handshake cookie") \ + _(DATA, "Data") \ + +typedef enum message_type +{ +#define _(v,s) MESSAGE_##v, + foreach_wg_message_type +#undef _ +} message_type_t; + +typedef struct message_header +{ + message_type_t type; +} message_header_t; + +typedef struct message_handshake_initiation +{ + message_header_t header; + u32 sender_index; + u8 unencrypted_ephemeral[NOISE_PUBLIC_KEY_LEN]; + u8 encrypted_static[noise_encrypted_len (NOISE_PUBLIC_KEY_LEN)]; + u8 encrypted_timestamp[noise_encrypted_len (NOISE_TIMESTAMP_LEN)]; + message_macs_t macs; +} message_handshake_initiation_t; + +typedef struct message_handshake_response +{ + message_header_t header; + u32 sender_index; + u32 receiver_index; + u8 unencrypted_ephemeral[NOISE_PUBLIC_KEY_LEN]; + u8 encrypted_nothing[noise_encrypted_len (0)]; + message_macs_t macs; +} message_handshake_response_t; + +typedef struct message_handshake_cookie +{ + message_header_t header; + u32 receiver_index; + u8 nonce[COOKIE_NONCE_SIZE]; + u8 encrypted_cookie[noise_encrypted_len (COOKIE_MAC_SIZE)]; +} message_handshake_cookie_t; + +typedef struct message_data +{ + message_header_t header; + u32 receiver_index; + u64 counter; + u8 encrypted_data[]; +} message_data_t; + +#define message_data_len(plain_len) \ + (noise_encrypted_len(plain_len) + sizeof(message_data_t)) + +#endif /* __included_wg_messages_h__ */ + +/* + * fd.io coding-style-patch-verification: ON + * + * Local Variables: + * eval: (c-set-style "gnu") + * End: + */ diff --git a/src/plugins/wireguard/wireguard_noise.c b/src/plugins/wireguard/wireguard_noise.c new file mode 100755 index 00000000000..666618a2a51 --- /dev/null +++ b/src/plugins/wireguard/wireguard_noise.c @@ -0,0 +1,985 @@ +/* + * Copyright (c) 2020 Doc.ai and/or its affiliates. + * Copyright (c) 2015-2020 Jason A. Donenfeld <Jason@zx2c4.com>. + * Copyright (c) 2019-2020 Matt Dunwoodie <ncon@noconroy.net>. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include <openssl/hmac.h> +#include <wireguard/wireguard.h> + +/* This implements Noise_IKpsk2: + * + * <- s + * ****** + * -> e, es, s, ss, {t} + * <- e, ee, se, psk, {} + */ + +/* Private functions */ +static noise_keypair_t *noise_remote_keypair_allocate (noise_remote_t *); +static void noise_remote_keypair_free (vlib_main_t * vm, noise_remote_t *, + noise_keypair_t **); +static uint32_t noise_remote_handshake_index_get (noise_remote_t *); +static void noise_remote_handshake_index_drop (noise_remote_t *); + +static uint64_t noise_counter_send (noise_counter_t *); +static bool noise_counter_recv (noise_counter_t *, uint64_t); + +static void noise_kdf (uint8_t *, uint8_t *, uint8_t *, const uint8_t *, + size_t, size_t, size_t, size_t, + const uint8_t[NOISE_HASH_LEN]); +static bool noise_mix_dh (uint8_t[NOISE_HASH_LEN], + uint8_t[NOISE_SYMMETRIC_KEY_LEN], + const uint8_t[NOISE_PUBLIC_KEY_LEN], + const uint8_t[NOISE_PUBLIC_KEY_LEN]); +static bool noise_mix_ss (uint8_t ck[NOISE_HASH_LEN], + uint8_t key[NOISE_SYMMETRIC_KEY_LEN], + const uint8_t ss[NOISE_PUBLIC_KEY_LEN]); +static void noise_mix_hash (uint8_t[NOISE_HASH_LEN], const uint8_t *, size_t); +static void noise_mix_psk (uint8_t[NOISE_HASH_LEN], + uint8_t[NOISE_HASH_LEN], + uint8_t[NOISE_SYMMETRIC_KEY_LEN], + const uint8_t[NOISE_SYMMETRIC_KEY_LEN]); +static void noise_param_init (uint8_t[NOISE_HASH_LEN], + uint8_t[NOISE_HASH_LEN], + const uint8_t[NOISE_PUBLIC_KEY_LEN]); + +static void noise_msg_encrypt (vlib_main_t * vm, uint8_t *, uint8_t *, size_t, + uint32_t key_idx, uint8_t[NOISE_HASH_LEN]); +static bool noise_msg_decrypt (vlib_main_t * vm, uint8_t *, uint8_t *, size_t, + uint32_t key_idx, uint8_t[NOISE_HASH_LEN]); +static void noise_msg_ephemeral (uint8_t[NOISE_HASH_LEN], + uint8_t[NOISE_HASH_LEN], + const uint8_t src[NOISE_PUBLIC_KEY_LEN]); + +static void noise_tai64n_now (uint8_t[NOISE_TIMESTAMP_LEN]); + +static void secure_zero_memory (void *v, size_t n); + +/* Set/Get noise parameters */ +void +noise_local_init (noise_local_t * l, struct noise_upcall *upcall) +{ + clib_memset (l, 0, sizeof (*l)); + l->l_upcall = *upcall; +} + +bool +noise_local_set_private (noise_local_t * l, + const uint8_t private[NOISE_PUBLIC_KEY_LEN]) +{ + clib_memcpy (l->l_private, private, NOISE_PUBLIC_KEY_LEN); + l->l_has_identity = curve25519_gen_public (l->l_public, private); + + return l->l_has_identity; +} + +bool +noise_local_keys (noise_local_t * l, uint8_t public[NOISE_PUBLIC_KEY_LEN], + uint8_t private[NOISE_PUBLIC_KEY_LEN]) +{ + if (l->l_has_identity) + { + if (public != NULL) + clib_memcpy (public, l->l_public, NOISE_PUBLIC_KEY_LEN); + if (private != NULL) + clib_memcpy (private, l->l_private, NOISE_PUBLIC_KEY_LEN); + } + else + { + return false; + } + return true; +} + +void +noise_remote_init (noise_remote_t * r, uint32_t peer_pool_idx, + const uint8_t public[NOISE_PUBLIC_KEY_LEN], + noise_local_t * l) +{ + clib_memset (r, 0, sizeof (*r)); + clib_memcpy (r->r_public, public, NOISE_PUBLIC_KEY_LEN); + r->r_peer_idx = peer_pool_idx; + + ASSERT (l != NULL); + r->r_local = l; + r->r_handshake.hs_state = HS_ZEROED; + noise_remote_precompute (r); +} + +bool +noise_remote_set_psk (noise_remote_t * r, + uint8_t psk[NOISE_SYMMETRIC_KEY_LEN]) +{ + int same; + same = !clib_memcmp (r->r_psk, psk, NOISE_SYMMETRIC_KEY_LEN); + if (!same) + { + clib_memcpy (r->r_psk, psk, NOISE_SYMMETRIC_KEY_LEN); + } + return same == 0; +} + +bool +noise_remote_keys (noise_remote_t * r, uint8_t public[NOISE_PUBLIC_KEY_LEN], + uint8_t psk[NOISE_SYMMETRIC_KEY_LEN]) +{ + static uint8_t null_psk[NOISE_SYMMETRIC_KEY_LEN]; + int ret; + + if (public != NULL) + clib_memcpy (public, r->r_public, NOISE_PUBLIC_KEY_LEN); + + if (psk != NULL) + clib_memcpy (psk, r->r_psk, NOISE_SYMMETRIC_KEY_LEN); + ret = clib_memcmp (r->r_psk, null_psk, NOISE_SYMMETRIC_KEY_LEN); + + return ret; +} + +void +noise_remote_precompute (noise_remote_t * r) +{ + noise_local_t *l = r->r_local; + if (!l->l_has_identity) + clib_memset (r->r_ss, 0, NOISE_PUBLIC_KEY_LEN); + else if (!curve25519_gen_shared (r->r_ss, l->l_private, r->r_public)) + clib_memset (r->r_ss, 0, NOISE_PUBLIC_KEY_LEN); + + noise_remote_handshake_index_drop (r); + secure_zero_memory (&r->r_handshake, sizeof (r->r_handshake)); +} + +/* Handshake functions */ +bool +noise_create_initiation (vlib_main_t * vm, noise_remote_t * r, + uint32_t * s_idx, uint8_t ue[NOISE_PUBLIC_KEY_LEN], + uint8_t es[NOISE_PUBLIC_KEY_LEN + NOISE_AUTHTAG_LEN], + uint8_t ets[NOISE_TIMESTAMP_LEN + NOISE_AUTHTAG_LEN]) +{ + noise_handshake_t *hs = &r->r_handshake; + noise_local_t *l = r->r_local; + uint8_t _key[NOISE_SYMMETRIC_KEY_LEN]; + uint32_t key_idx; + uint8_t *key; + int ret = false; + + key_idx = + vnet_crypto_key_add (vm, VNET_CRYPTO_ALG_CHACHA20_POLY1305, _key, + NOISE_SYMMETRIC_KEY_LEN); + key = vnet_crypto_get_key (key_idx)->data; + + if (!l->l_has_identity) + goto error; + noise_param_init (hs->hs_ck, hs->hs_hash, r->r_public); + + /* e */ + curve25519_gen_secret (hs->hs_e); + if (!curve25519_gen_public (ue, hs->hs_e)) + goto error; + noise_msg_ephemeral (hs->hs_ck, hs->hs_hash, ue); + + /* es */ + if (!noise_mix_dh (hs->hs_ck, key, hs->hs_e, r->r_public)) + goto error; + + /* s */ + noise_msg_encrypt (vm, es, l->l_public, NOISE_PUBLIC_KEY_LEN, key_idx, + hs->hs_hash); + + /* ss */ + if (!noise_mix_ss (hs->hs_ck, key, r->r_ss)) + goto error; + + /* {t} */ + noise_tai64n_now (ets); + noise_msg_encrypt (vm, ets, ets, NOISE_TIMESTAMP_LEN, key_idx, hs->hs_hash); + noise_remote_handshake_index_drop (r); + hs->hs_state = CREATED_INITIATION; + hs->hs_local_index = noise_remote_handshake_index_get (r); + *s_idx = hs->hs_local_index; + ret = true; +error: + vnet_crypto_key_del (vm, key_idx); + secure_zero_memory (key, NOISE_SYMMETRIC_KEY_LEN); + return ret; +} + +bool +noise_consume_initiation (vlib_main_t * vm, noise_local_t * l, + noise_remote_t ** rp, uint32_t s_idx, + uint8_t ue[NOISE_PUBLIC_KEY_LEN], + uint8_t es[NOISE_PUBLIC_KEY_LEN + + NOISE_AUTHTAG_LEN], + uint8_t ets[NOISE_TIMESTAMP_LEN + + NOISE_AUTHTAG_LEN]) +{ + noise_remote_t *r; + noise_handshake_t hs; + uint8_t _key[NOISE_SYMMETRIC_KEY_LEN]; + uint8_t r_public[NOISE_PUBLIC_KEY_LEN]; + uint8_t timestamp[NOISE_TIMESTAMP_LEN]; + u32 key_idx; + uint8_t *key; + int ret = false; + + key_idx = + vnet_crypto_key_add (vm, VNET_CRYPTO_ALG_CHACHA20_POLY1305, _key, + NOISE_SYMMETRIC_KEY_LEN); + key = vnet_crypto_get_key (key_idx)->data; + + if (!l->l_has_identity) + goto error; + noise_param_init (hs.hs_ck, hs.hs_hash, l->l_public); + + /* e */ + noise_msg_ephemeral (hs.hs_ck, hs.hs_hash, ue); + + /* es */ + if (!noise_mix_dh (hs.hs_ck, key, l->l_private, ue)) + goto error; + + /* s */ + + if (!noise_msg_decrypt (vm, r_public, es, + NOISE_PUBLIC_KEY_LEN + NOISE_AUTHTAG_LEN, key_idx, + hs.hs_hash)) + goto error; + + /* Lookup the remote we received from */ + if ((r = l->l_upcall.u_remote_get (r_public)) == NULL) + goto error; + + /* ss */ + if (!noise_mix_ss (hs.hs_ck, key, r->r_ss)) + goto error; + + /* {t} */ + if (!noise_msg_decrypt (vm, timestamp, ets, + NOISE_TIMESTAMP_LEN + NOISE_AUTHTAG_LEN, key_idx, + hs.hs_hash)) + goto error; + ; + + hs.hs_state = CONSUMED_INITIATION; + hs.hs_local_index = 0; + hs.hs_remote_index = s_idx; + clib_memcpy (hs.hs_e, ue, NOISE_PUBLIC_KEY_LEN); + + /* Replay */ + if (clib_memcmp (timestamp, r->r_timestamp, NOISE_TIMESTAMP_LEN) > 0) + clib_memcpy (r->r_timestamp, timestamp, NOISE_TIMESTAMP_LEN); + else + goto error; + + /* Flood attack */ + if (wg_birthdate_has_expired (r->r_last_init, REJECT_INTERVAL)) + r->r_last_init = vlib_time_now (vm); + else + goto error; + + /* Ok, we're happy to accept this initiation now */ + noise_remote_handshake_index_drop (r); + r->r_handshake = hs; + *rp = r; + ret = true; +error: + vnet_crypto_key_del (vm, key_idx); + secure_zero_memory (key, NOISE_SYMMETRIC_KEY_LEN); + secure_zero_memory (&hs, sizeof (hs)); + return ret; +} + +bool +noise_create_response (vlib_main_t * vm, noise_remote_t * r, uint32_t * s_idx, + uint32_t * r_idx, uint8_t ue[NOISE_PUBLIC_KEY_LEN], + uint8_t en[0 + NOISE_AUTHTAG_LEN]) +{ + noise_handshake_t *hs = &r->r_handshake; + uint8_t _key[NOISE_SYMMETRIC_KEY_LEN]; + uint8_t e[NOISE_PUBLIC_KEY_LEN]; + uint32_t key_idx; + uint8_t *key; + int ret = false; + + key_idx = + vnet_crypto_key_add (vm, VNET_CRYPTO_ALG_CHACHA20_POLY1305, _key, + NOISE_SYMMETRIC_KEY_LEN); + key = vnet_crypto_get_key (key_idx)->data; + + if (hs->hs_state != CONSUMED_INITIATION) + goto error; + + /* e */ + curve25519_gen_secret (e); + if (!curve25519_gen_public (ue, e)) + goto error; + noise_msg_ephemeral (hs->hs_ck, hs->hs_hash, ue); + + /* ee */ + if (!noise_mix_dh (hs->hs_ck, NULL, e, hs->hs_e)) + goto error; + + /* se */ + if (!noise_mix_dh (hs->hs_ck, NULL, e, r->r_public)) + goto error; + + /* psk */ + noise_mix_psk (hs->hs_ck, hs->hs_hash, key, r->r_psk); + + /* {} */ + noise_msg_encrypt (vm, en, NULL, 0, key_idx, hs->hs_hash); + + + hs->hs_state = CREATED_RESPONSE; + hs->hs_local_index = noise_remote_handshake_index_get (r); + *r_idx = hs->hs_remote_index; + *s_idx = hs->hs_local_index; + ret = true; +error: + vnet_crypto_key_del (vm, key_idx); + secure_zero_memory (key, NOISE_SYMMETRIC_KEY_LEN); + secure_zero_memory (e, NOISE_PUBLIC_KEY_LEN); + return ret; +} + +bool +noise_consume_response (vlib_main_t * vm, noise_remote_t * r, uint32_t s_idx, + uint32_t r_idx, uint8_t ue[NOISE_PUBLIC_KEY_LEN], + uint8_t en[0 + NOISE_AUTHTAG_LEN]) +{ + noise_local_t *l = r->r_local; + noise_handshake_t hs; + uint8_t _key[NOISE_SYMMETRIC_KEY_LEN]; + uint8_t preshared_key[NOISE_PUBLIC_KEY_LEN]; + uint32_t key_idx; + uint8_t *key; + int ret = false; + + key_idx = + vnet_crypto_key_add (vm, VNET_CRYPTO_ALG_CHACHA20_POLY1305, _key, + NOISE_SYMMETRIC_KEY_LEN); + key = vnet_crypto_get_key (key_idx)->data; + + if (!l->l_has_identity) + goto error; + + hs = r->r_handshake; + clib_memcpy (preshared_key, r->r_psk, NOISE_SYMMETRIC_KEY_LEN); + + if (hs.hs_state != CREATED_INITIATION || hs.hs_local_index != r_idx) + goto error; + + /* e */ + noise_msg_ephemeral (hs.hs_ck, hs.hs_hash, ue); + + /* ee */ + if (!noise_mix_dh (hs.hs_ck, NULL, hs.hs_e, ue)) + goto error; + + /* se */ + if (!noise_mix_dh (hs.hs_ck, NULL, l->l_private, ue)) + goto error; + + /* psk */ + noise_mix_psk (hs.hs_ck, hs.hs_hash, key, preshared_key); + + /* {} */ + + if (!noise_msg_decrypt + (vm, NULL, en, 0 + NOISE_AUTHTAG_LEN, key_idx, hs.hs_hash)) + goto error; + + + hs.hs_remote_index = s_idx; + + if (r->r_handshake.hs_state == hs.hs_state && + r->r_handshake.hs_local_index == hs.hs_local_index) + { + r->r_handshake = hs; + r->r_handshake.hs_state = CONSUMED_RESPONSE; + ret = true; + } +error: + vnet_crypto_key_del (vm, key_idx); + secure_zero_memory (&hs, sizeof (hs)); + secure_zero_memory (key, NOISE_SYMMETRIC_KEY_LEN); + return ret; +} + +bool +noise_remote_begin_session (vlib_main_t * vm, noise_remote_t * r) +{ + noise_handshake_t *hs = &r->r_handshake; + noise_keypair_t kp, *next, *current, *previous; + + uint8_t key_send[NOISE_SYMMETRIC_KEY_LEN]; + uint8_t key_recv[NOISE_SYMMETRIC_KEY_LEN]; + + /* We now derive the keypair from the handshake */ + if (hs->hs_state == CONSUMED_RESPONSE) + { + kp.kp_is_initiator = 1; + noise_kdf (key_send, key_recv, NULL, NULL, + NOISE_SYMMETRIC_KEY_LEN, NOISE_SYMMETRIC_KEY_LEN, 0, 0, + hs->hs_ck); + } + else if (hs->hs_state == CREATED_RESPONSE) + { + kp.kp_is_initiator = 0; + noise_kdf (key_recv, key_send, NULL, NULL, + NOISE_SYMMETRIC_KEY_LEN, NOISE_SYMMETRIC_KEY_LEN, 0, 0, + hs->hs_ck); + } + else + { + return false; + } + + kp.kp_valid = 1; + kp.kp_send_index = vnet_crypto_key_add (vm, + VNET_CRYPTO_ALG_CHACHA20_POLY1305, + key_send, NOISE_SYMMETRIC_KEY_LEN); + kp.kp_recv_index = vnet_crypto_key_add (vm, + VNET_CRYPTO_ALG_CHACHA20_POLY1305, + key_recv, NOISE_SYMMETRIC_KEY_LEN); + kp.kp_local_index = hs->hs_local_index; + kp.kp_remote_index = hs->hs_remote_index; + kp.kp_birthdate = vlib_time_now (vm); + clib_memset (&kp.kp_ctr, 0, sizeof (kp.kp_ctr)); + + /* Now we need to add_new_keypair */ + next = r->r_next; + current = r->r_current; + previous = r->r_previous; + + if (kp.kp_is_initiator) + { + if (next != NULL) + { + r->r_next = NULL; + r->r_previous = next; + noise_remote_keypair_free (vm, r, ¤t); + } + else + { + r->r_previous = current; + } + + noise_remote_keypair_free (vm, r, &previous); + + r->r_current = noise_remote_keypair_allocate (r); + *r->r_current = kp; + } + else + { + noise_remote_keypair_free (vm, r, &next); + r->r_previous = NULL; + noise_remote_keypair_free (vm, r, &previous); + + r->r_next = noise_remote_keypair_allocate (r); + *r->r_next = kp; + } + secure_zero_memory (&r->r_handshake, sizeof (r->r_handshake)); + secure_zero_memory (&kp, sizeof (kp)); + return true; +} + +void +noise_remote_clear (vlib_main_t * vm, noise_remote_t * r) +{ + noise_remote_handshake_index_drop (r); + secure_zero_memory (&r->r_handshake, sizeof (r->r_handshake)); + + noise_remote_keypair_free (vm, r, &r->r_next); + noise_remote_keypair_free (vm, r, &r->r_current); + noise_remote_keypair_free (vm, r, &r->r_previous); + r->r_next = NULL; + r->r_current = NULL; + r->r_previous = NULL; +} + +void +noise_remote_expire_current (noise_remote_t * r) +{ + if (r->r_next != NULL) + r->r_next->kp_valid = 0; + if (r->r_current != NULL) + r->r_current->kp_valid = 0; +} + +bool +noise_remote_ready (noise_remote_t * r) +{ + noise_keypair_t *kp; + int ret; + + if ((kp = r->r_current) == NULL || + !kp->kp_valid || + wg_birthdate_has_expired (kp->kp_birthdate, REJECT_AFTER_TIME) || + kp->kp_ctr.c_recv >= REJECT_AFTER_MESSAGES || + kp->kp_ctr.c_send >= REJECT_AFTER_MESSAGES) + ret = false; + else + ret = true; + return ret; +} + +static void +chacha20poly1305_calc (vlib_main_t * vm, + u8 * src, + u32 src_len, + u8 * dst, + u8 * aad, + u32 aad_len, + u64 nonce, + vnet_crypto_op_id_t op_id, + vnet_crypto_key_index_t key_index) +{ + u8 iv[12]; + clib_memset (iv, 0, 12); + clib_memcpy (iv + 4, &nonce, sizeof (nonce)); + + vnet_crypto_op_t _op, *op = &_op; + + u8 _tag[16] = { }; + if (op_id == VNET_CRYPTO_OP_CHACHA20_POLY1305_DEC) + { + clib_memcpy (_tag, src + src_len - NOISE_AUTHTAG_LEN, + NOISE_AUTHTAG_LEN); + src_len -= NOISE_AUTHTAG_LEN; + } + vnet_crypto_op_init (op, op_id); + op->key_index = key_index; + op->src = src; + op->dst = dst; + op->len = src_len; + op->aad = aad; + op->aad_len = aad_len; + op->iv = iv; + op->tag_len = NOISE_AUTHTAG_LEN; + op->tag = _tag; + vnet_crypto_process_ops (vm, op, 1); + if (op_id == VNET_CRYPTO_OP_CHACHA20_POLY1305_ENC) + { + clib_memcpy (dst + src_len, op->tag, NOISE_AUTHTAG_LEN); + } +} + +enum noise_state_crypt +noise_remote_encrypt (vlib_main_t * vm, noise_remote_t * r, uint32_t * r_idx, + uint64_t * nonce, uint8_t * src, size_t srclen, + uint8_t * dst) +{ + noise_keypair_t *kp; + enum noise_state_crypt ret = SC_FAILED; + + if ((kp = r->r_current) == NULL) + goto error; + + /* We confirm that our values are within our tolerances. We want: + * - a valid keypair + * - our keypair to be less than REJECT_AFTER_TIME seconds old + * - our receive counter to be less than REJECT_AFTER_MESSAGES + * - our send counter to be less than REJECT_AFTER_MESSAGES + */ + if (!kp->kp_valid || + wg_birthdate_has_expired (kp->kp_birthdate, REJECT_AFTER_TIME) || + kp->kp_ctr.c_recv >= REJECT_AFTER_MESSAGES || + ((*nonce = noise_counter_send (&kp->kp_ctr)) > REJECT_AFTER_MESSAGES)) + goto error; + + /* We encrypt into the same buffer, so the caller must ensure that buf + * has NOISE_AUTHTAG_LEN bytes to store the MAC. The nonce and index + * are passed back out to the caller through the provided data pointer. */ + *r_idx = kp->kp_remote_index; + + chacha20poly1305_calc (vm, src, srclen, dst, NULL, 0, *nonce, + VNET_CRYPTO_OP_CHACHA20_POLY1305_ENC, + kp->kp_send_index); + + /* If our values are still within tolerances, but we are approaching + * the tolerances, we notify the caller with ESTALE that they should + * establish a new keypair. The current keypair can continue to be used + * until the tolerances are hit. We notify if: + * - our send counter is valid and not less than REKEY_AFTER_MESSAGES + * - we're the initiator and our keypair is older than + * REKEY_AFTER_TIME seconds */ + ret = SC_KEEP_KEY_FRESH; + if ((kp->kp_valid && *nonce >= REKEY_AFTER_MESSAGES) || + (kp->kp_is_initiator && + wg_birthdate_has_expired (kp->kp_birthdate, REKEY_AFTER_TIME))) + goto error; + + ret = SC_OK; +error: + return ret; +} + +enum noise_state_crypt +noise_remote_decrypt (vlib_main_t * vm, noise_remote_t * r, uint32_t r_idx, + uint64_t nonce, uint8_t * src, size_t srclen, + uint8_t * dst) +{ + noise_keypair_t *kp; + enum noise_state_crypt ret = SC_FAILED; + + if (r->r_current != NULL && r->r_current->kp_local_index == r_idx) + { + kp = r->r_current; + } + else if (r->r_previous != NULL && r->r_previous->kp_local_index == r_idx) + { + kp = r->r_previous; + } + else if (r->r_next != NULL && r->r_next->kp_local_index == r_idx) + { + kp = r->r_next; + } + else + { + goto error; + } + + /* We confirm that our values are within our tolerances. These values + * are the same as the encrypt routine. + * + * kp_ctr isn't locked here, we're happy to accept a racy read. */ + if (wg_birthdate_has_expired (kp->kp_birthdate, REJECT_AFTER_TIME) || + kp->kp_ctr.c_recv >= REJECT_AFTER_MESSAGES) + goto error; + + /* Decrypt, then validate the counter. We don't want to validate the + * counter before decrypting as we do not know the message is authentic + * prior to decryption. */ + chacha20poly1305_calc (vm, src, srclen, dst, NULL, 0, nonce, + VNET_CRYPTO_OP_CHACHA20_POLY1305_DEC, + kp->kp_recv_index); + + if (!noise_counter_recv (&kp->kp_ctr, nonce)) + goto error; + + /* If we've received the handshake confirming data packet then move the + * next keypair into current. If we do slide the next keypair in, then + * we skip the REKEY_AFTER_TIME_RECV check. This is safe to do as a + * data packet can't confirm a session that we are an INITIATOR of. */ + if (kp == r->r_next && kp->kp_local_index == r_idx) + { + noise_remote_keypair_free (vm, r, &r->r_previous); + r->r_previous = r->r_current; + r->r_current = r->r_next; + r->r_next = NULL; + + ret = SC_CONN_RESET; + goto error; + } + + + /* Similar to when we encrypt, we want to notify the caller when we + * are approaching our tolerances. We notify if: + * - we're the initiator and the current keypair is older than + * REKEY_AFTER_TIME_RECV seconds. */ + ret = SC_KEEP_KEY_FRESH; + kp = r->r_current; + if (kp != NULL && + kp->kp_valid && + kp->kp_is_initiator && + wg_birthdate_has_expired (kp->kp_birthdate, REKEY_AFTER_TIME_RECV)) + goto error; + + ret = SC_OK; +error: + return ret; +} + +/* Private functions - these should not be called outside this file under any + * circumstances. */ +static noise_keypair_t * +noise_remote_keypair_allocate (noise_remote_t * r) +{ + noise_keypair_t *kp; + kp = clib_mem_alloc (sizeof (*kp)); + return kp; +} + +static void +noise_remote_keypair_free (vlib_main_t * vm, noise_remote_t * r, + noise_keypair_t ** kp) +{ + struct noise_upcall *u = &r->r_local->l_upcall; + if (*kp) + { + u->u_index_drop ((*kp)->kp_local_index); + vnet_crypto_key_del (vm, (*kp)->kp_send_index); + vnet_crypto_key_del (vm, (*kp)->kp_recv_index); + clib_mem_free (*kp); + } +} + +static uint32_t +noise_remote_handshake_index_get (noise_remote_t * r) +{ + struct noise_upcall *u = &r->r_local->l_upcall; + return u->u_index_set (r); +} + +static void +noise_remote_handshake_index_drop (noise_remote_t * r) +{ + noise_handshake_t *hs = &r->r_handshake; + struct noise_upcall *u = &r->r_local->l_upcall; + if (hs->hs_state != HS_ZEROED) + u->u_index_drop (hs->hs_local_index); +} + +static uint64_t +noise_counter_send (noise_counter_t * ctr) +{ + uint64_t ret = ctr->c_send++; + return ret; +} + +static bool +noise_counter_recv (noise_counter_t * ctr, uint64_t recv) +{ + uint64_t i, top, index_recv, index_ctr; + unsigned long bit; + bool ret = false; + + + /* Check that the recv counter is valid */ + if (ctr->c_recv >= REJECT_AFTER_MESSAGES || recv >= REJECT_AFTER_MESSAGES) + goto error; + + /* If the packet is out of the window, invalid */ + if (recv + COUNTER_WINDOW_SIZE < ctr->c_recv) + goto error; + + /* If the new counter is ahead of the current counter, we'll need to + * zero out the bitmap that has previously been used */ + index_recv = recv / COUNTER_BITS; + index_ctr = ctr->c_recv / COUNTER_BITS; + + if (recv > ctr->c_recv) + { + top = clib_min (index_recv - index_ctr, COUNTER_NUM); + for (i = 1; i <= top; i++) + ctr->c_backtrack[(i + index_ctr) & (COUNTER_NUM - 1)] = 0; + ctr->c_recv = recv; + } + + index_recv %= COUNTER_NUM; + bit = 1ul << (recv % COUNTER_BITS); + + if (ctr->c_backtrack[index_recv] & bit) + goto error; + + ctr->c_backtrack[index_recv] |= bit; + + ret = true; +error: + return ret; +} + +static void +noise_kdf (uint8_t * a, uint8_t * b, uint8_t * c, const uint8_t * x, + size_t a_len, size_t b_len, size_t c_len, size_t x_len, + const uint8_t ck[NOISE_HASH_LEN]) +{ + uint8_t out[BLAKE2S_HASH_SIZE + 1]; + uint8_t sec[BLAKE2S_HASH_SIZE]; + + /* Extract entropy from "x" into sec */ + u32 l = 0; + HMAC (EVP_blake2s256 (), ck, NOISE_HASH_LEN, x, x_len, sec, &l); + ASSERT (l == BLAKE2S_HASH_SIZE); + if (a == NULL || a_len == 0) + goto out; + + /* Expand first key: key = sec, data = 0x1 */ + out[0] = 1; + HMAC (EVP_blake2s256 (), sec, BLAKE2S_HASH_SIZE, out, 1, out, &l); + ASSERT (l == BLAKE2S_HASH_SIZE); + clib_memcpy (a, out, a_len); + + if (b == NULL || b_len == 0) + goto out; + + /* Expand second key: key = sec, data = "a" || 0x2 */ + out[BLAKE2S_HASH_SIZE] = 2; + HMAC (EVP_blake2s256 (), sec, BLAKE2S_HASH_SIZE, out, BLAKE2S_HASH_SIZE + 1, + out, &l); + ASSERT (l == BLAKE2S_HASH_SIZE); + clib_memcpy (b, out, b_len); + + if (c == NULL || c_len == 0) + goto out; + + /* Expand third key: key = sec, data = "b" || 0x3 */ + out[BLAKE2S_HASH_SIZE] = 3; + HMAC (EVP_blake2s256 (), sec, BLAKE2S_HASH_SIZE, out, BLAKE2S_HASH_SIZE + 1, + out, &l); + ASSERT (l == BLAKE2S_HASH_SIZE); + + clib_memcpy (c, out, c_len); + +out: + /* Clear sensitive data from stack */ + secure_zero_memory (sec, BLAKE2S_HASH_SIZE); + secure_zero_memory (out, BLAKE2S_HASH_SIZE + 1); +} + +static bool +noise_mix_dh (uint8_t ck[NOISE_HASH_LEN], + uint8_t key[NOISE_SYMMETRIC_KEY_LEN], + const uint8_t private[NOISE_PUBLIC_KEY_LEN], + const uint8_t public[NOISE_PUBLIC_KEY_LEN]) +{ + uint8_t dh[NOISE_PUBLIC_KEY_LEN]; + if (!curve25519_gen_shared (dh, private, public)) + return false; + noise_kdf (ck, key, NULL, dh, + NOISE_HASH_LEN, NOISE_SYMMETRIC_KEY_LEN, 0, NOISE_PUBLIC_KEY_LEN, + ck); + secure_zero_memory (dh, NOISE_PUBLIC_KEY_LEN); + return true; +} + +static bool +noise_mix_ss (uint8_t ck[NOISE_HASH_LEN], + uint8_t key[NOISE_SYMMETRIC_KEY_LEN], + const uint8_t ss[NOISE_PUBLIC_KEY_LEN]) +{ + static uint8_t null_point[NOISE_PUBLIC_KEY_LEN]; + if (clib_memcmp (ss, null_point, NOISE_PUBLIC_KEY_LEN) == 0) + return false; + noise_kdf (ck, key, NULL, ss, + NOISE_HASH_LEN, NOISE_SYMMETRIC_KEY_LEN, 0, NOISE_PUBLIC_KEY_LEN, + ck); + return true; +} + +static void +noise_mix_hash (uint8_t hash[NOISE_HASH_LEN], const uint8_t * src, + size_t src_len) +{ + blake2s_state_t blake; + + blake2s_init (&blake, NOISE_HASH_LEN); + blake2s_update (&blake, hash, NOISE_HASH_LEN); + blake2s_update (&blake, src, src_len); + blake2s_final (&blake, hash, NOISE_HASH_LEN); +} + +static void +noise_mix_psk (uint8_t ck[NOISE_HASH_LEN], uint8_t hash[NOISE_HASH_LEN], + uint8_t key[NOISE_SYMMETRIC_KEY_LEN], + const uint8_t psk[NOISE_SYMMETRIC_KEY_LEN]) +{ + uint8_t tmp[NOISE_HASH_LEN]; + + noise_kdf (ck, tmp, key, psk, + NOISE_HASH_LEN, NOISE_HASH_LEN, NOISE_SYMMETRIC_KEY_LEN, + NOISE_SYMMETRIC_KEY_LEN, ck); + noise_mix_hash (hash, tmp, NOISE_HASH_LEN); + secure_zero_memory (tmp, NOISE_HASH_LEN); +} + +static void +noise_param_init (uint8_t ck[NOISE_HASH_LEN], uint8_t hash[NOISE_HASH_LEN], + const uint8_t s[NOISE_PUBLIC_KEY_LEN]) +{ + blake2s_state_t blake; + + blake2s (ck, NOISE_HASH_LEN, (uint8_t *) NOISE_HANDSHAKE_NAME, + strlen (NOISE_HANDSHAKE_NAME), NULL, 0); + + blake2s_init (&blake, NOISE_HASH_LEN); + blake2s_update (&blake, ck, NOISE_HASH_LEN); + blake2s_update (&blake, (uint8_t *) NOISE_IDENTIFIER_NAME, + strlen (NOISE_IDENTIFIER_NAME)); + blake2s_final (&blake, hash, NOISE_HASH_LEN); + + noise_mix_hash (hash, s, NOISE_PUBLIC_KEY_LEN); +} + +static void +noise_msg_encrypt (vlib_main_t * vm, uint8_t * dst, uint8_t * src, + size_t src_len, uint32_t key_idx, + uint8_t hash[NOISE_HASH_LEN]) +{ + /* Nonce always zero for Noise_IK */ + chacha20poly1305_calc (vm, src, src_len, dst, hash, NOISE_HASH_LEN, 0, + VNET_CRYPTO_OP_CHACHA20_POLY1305_ENC, key_idx); + noise_mix_hash (hash, dst, src_len + NOISE_AUTHTAG_LEN); +} + +static bool +noise_msg_decrypt (vlib_main_t * vm, uint8_t * dst, uint8_t * src, + size_t src_len, uint32_t key_idx, + uint8_t hash[NOISE_HASH_LEN]) +{ + /* Nonce always zero for Noise_IK */ + chacha20poly1305_calc (vm, src, src_len, dst, hash, NOISE_HASH_LEN, 0, + VNET_CRYPTO_OP_CHACHA20_POLY1305_DEC, key_idx); + noise_mix_hash (hash, src, src_len); + return true; +} + +static void +noise_msg_ephemeral (uint8_t ck[NOISE_HASH_LEN], uint8_t hash[NOISE_HASH_LEN], + const uint8_t src[NOISE_PUBLIC_KEY_LEN]) +{ + noise_mix_hash (hash, src, NOISE_PUBLIC_KEY_LEN); + noise_kdf (ck, NULL, NULL, src, NOISE_HASH_LEN, 0, 0, + NOISE_PUBLIC_KEY_LEN, ck); +} + +static void +noise_tai64n_now (uint8_t output[NOISE_TIMESTAMP_LEN]) +{ + uint32_t unix_sec; + uint32_t unix_nanosec; + + uint64_t sec; + uint32_t nsec; + + unix_time_now_nsec_fraction (&unix_sec, &unix_nanosec); + + /* Round down the nsec counter to limit precise timing leak. */ + unix_nanosec &= REJECT_INTERVAL_MASK; + + /* https://cr.yp.to/libtai/tai64.html */ + sec = htobe64 (0x400000000000000aULL + unix_sec); + nsec = htobe32 (unix_nanosec); + + /* memcpy to output buffer, assuming output could be unaligned. */ + clib_memcpy (output, &sec, sizeof (sec)); + clib_memcpy (output + sizeof (sec), &nsec, sizeof (nsec)); +} + +static void +secure_zero_memory (void *v, size_t n) +{ + static void *(*const volatile memset_v) (void *, int, size_t) = &memset; + memset_v (v, 0, n); +} + +/* + * fd.io coding-style-patch-verification: ON + * + * Local Variables: + * eval: (c-set-style "gnu") + * End: + */ diff --git a/src/plugins/wireguard/wireguard_noise.h b/src/plugins/wireguard/wireguard_noise.h new file mode 100755 index 00000000000..1f6804c59ca --- /dev/null +++ b/src/plugins/wireguard/wireguard_noise.h @@ -0,0 +1,202 @@ +/* + * Copyright (c) 2020 Doc.ai and/or its affiliates. + * Copyright (c) 2015-2020 Jason A. Donenfeld <Jason@zx2c4.com>. + * Copyright (c) 2019-2020 Matt Dunwoodie <ncon@noconroy.net>. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef __included_wg_noise_h__ +#define __included_wg_noise_h__ + +#include <vlib/vlib.h> +#include <vnet/crypto/crypto.h> +#include <wireguard/blake/blake2s.h> +#include <wireguard/wireguard_key.h> + +#define NOISE_PUBLIC_KEY_LEN CURVE25519_KEY_SIZE +#define NOISE_SYMMETRIC_KEY_LEN 32 // CHACHA20POLY1305_KEY_SIZE +#define NOISE_TIMESTAMP_LEN (sizeof(uint64_t) + sizeof(uint32_t)) +#define NOISE_AUTHTAG_LEN 16 //CHACHA20POLY1305_AUTHTAG_SIZE +#define NOISE_HASH_LEN BLAKE2S_HASH_SIZE + +/* Protocol string constants */ +#define NOISE_HANDSHAKE_NAME "Noise_IKpsk2_25519_ChaChaPoly_BLAKE2s" +#define NOISE_IDENTIFIER_NAME "WireGuard v1 zx2c4 Jason@zx2c4.com" + +/* Constants for the counter */ +#define COUNTER_BITS_TOTAL 8192 +#define COUNTER_BITS (sizeof(unsigned long) * 8) +#define COUNTER_NUM (COUNTER_BITS_TOTAL / COUNTER_BITS) +#define COUNTER_WINDOW_SIZE (COUNTER_BITS_TOTAL - COUNTER_BITS) + +/* Constants for the keypair */ +#define REKEY_AFTER_MESSAGES (1ull << 60) +#define REJECT_AFTER_MESSAGES (UINT64_MAX - COUNTER_WINDOW_SIZE - 1) +#define REKEY_AFTER_TIME 120 +#define REKEY_AFTER_TIME_RECV 165 +#define REJECT_AFTER_TIME 180 +#define REJECT_INTERVAL (0.02) /* fifty times per sec */ +/* 24 = floor(log2(REJECT_INTERVAL)) */ +#define REJECT_INTERVAL_MASK (~((1ull<<24)-1)) + +enum noise_state_crypt +{ + SC_OK = 0, + SC_CONN_RESET, + SC_KEEP_KEY_FRESH, + SC_FAILED, +}; + +enum noise_state_hs +{ + HS_ZEROED = 0, + CREATED_INITIATION, + CONSUMED_INITIATION, + CREATED_RESPONSE, + CONSUMED_RESPONSE, +}; + +typedef struct noise_handshake +{ + enum noise_state_hs hs_state; + uint32_t hs_local_index; + uint32_t hs_remote_index; + uint8_t hs_e[NOISE_PUBLIC_KEY_LEN]; + uint8_t hs_hash[NOISE_HASH_LEN]; + uint8_t hs_ck[NOISE_HASH_LEN]; +} noise_handshake_t; + +typedef struct noise_counter +{ + uint64_t c_send; + uint64_t c_recv; + unsigned long c_backtrack[COUNTER_NUM]; +} noise_counter_t; + +typedef struct noise_keypair +{ + int kp_valid; + int kp_is_initiator; + uint32_t kp_local_index; + uint32_t kp_remote_index; + vnet_crypto_key_index_t kp_send_index; + vnet_crypto_key_index_t kp_recv_index; + f64 kp_birthdate; + noise_counter_t kp_ctr; +} noise_keypair_t; + +typedef struct noise_local noise_local_t; +typedef struct noise_remote +{ + uint32_t r_peer_idx; + uint8_t r_public[NOISE_PUBLIC_KEY_LEN]; + noise_local_t *r_local; + uint8_t r_ss[NOISE_PUBLIC_KEY_LEN]; + + noise_handshake_t r_handshake; + uint8_t r_psk[NOISE_SYMMETRIC_KEY_LEN]; + uint8_t r_timestamp[NOISE_TIMESTAMP_LEN]; + f64 r_last_init; + + noise_keypair_t *r_next, *r_current, *r_previous; +} noise_remote_t; + +typedef struct noise_local +{ + bool l_has_identity; + uint8_t l_public[NOISE_PUBLIC_KEY_LEN]; + uint8_t l_private[NOISE_PUBLIC_KEY_LEN]; + + struct noise_upcall + { + void *u_arg; + noise_remote_t *(*u_remote_get) (uint8_t[NOISE_PUBLIC_KEY_LEN]); + uint32_t (*u_index_set) (noise_remote_t *); + void (*u_index_drop) (uint32_t); + } l_upcall; +} noise_local_t; + +/* Set/Get noise parameters */ +void noise_local_init (noise_local_t *, struct noise_upcall *); +bool noise_local_set_private (noise_local_t *, + const uint8_t[NOISE_PUBLIC_KEY_LEN]); +bool noise_local_keys (noise_local_t *, uint8_t[NOISE_PUBLIC_KEY_LEN], + uint8_t[NOISE_PUBLIC_KEY_LEN]); + +void noise_remote_init (noise_remote_t *, uint32_t, + const uint8_t[NOISE_PUBLIC_KEY_LEN], noise_local_t *); +bool noise_remote_set_psk (noise_remote_t *, + uint8_t[NOISE_SYMMETRIC_KEY_LEN]); +bool noise_remote_keys (noise_remote_t *, uint8_t[NOISE_PUBLIC_KEY_LEN], + uint8_t[NOISE_SYMMETRIC_KEY_LEN]); + +/* Should be called anytime noise_local_set_private is called */ +void noise_remote_precompute (noise_remote_t *); + +/* Cryptographic functions */ +bool noise_create_initiation (vlib_main_t * vm, noise_remote_t *, + uint32_t * s_idx, + uint8_t ue[NOISE_PUBLIC_KEY_LEN], + uint8_t es[NOISE_PUBLIC_KEY_LEN + + NOISE_AUTHTAG_LEN], + uint8_t ets[NOISE_TIMESTAMP_LEN + + NOISE_AUTHTAG_LEN]); + +bool noise_consume_initiation (vlib_main_t * vm, noise_local_t *, + noise_remote_t **, + uint32_t s_idx, + uint8_t ue[NOISE_PUBLIC_KEY_LEN], + uint8_t es[NOISE_PUBLIC_KEY_LEN + + NOISE_AUTHTAG_LEN], + uint8_t ets[NOISE_TIMESTAMP_LEN + + NOISE_AUTHTAG_LEN]); + +bool noise_create_response (vlib_main_t * vm, noise_remote_t *, + uint32_t * s_idx, + uint32_t * r_idx, + uint8_t ue[NOISE_PUBLIC_KEY_LEN], + uint8_t en[0 + NOISE_AUTHTAG_LEN]); + +bool noise_consume_response (vlib_main_t * vm, noise_remote_t *, + uint32_t s_idx, + uint32_t r_idx, + uint8_t ue[NOISE_PUBLIC_KEY_LEN], + uint8_t en[0 + NOISE_AUTHTAG_LEN]); + +bool noise_remote_begin_session (vlib_main_t * vm, noise_remote_t * r); +void noise_remote_clear (vlib_main_t * vm, noise_remote_t * r); +void noise_remote_expire_current (noise_remote_t * r); + +bool noise_remote_ready (noise_remote_t *); + +enum noise_state_crypt +noise_remote_encrypt (vlib_main_t * vm, noise_remote_t *, + uint32_t * r_idx, + uint64_t * nonce, + uint8_t * src, size_t srclen, uint8_t * dst); +enum noise_state_crypt +noise_remote_decrypt (vlib_main_t * vm, noise_remote_t *, + uint32_t r_idx, + uint64_t nonce, + uint8_t * src, size_t srclen, uint8_t * dst); + + +#endif /* __included_wg_noise_h__ */ + +/* + * fd.io coding-style-patch-verification: ON + * + * Local Variables: + * eval: (c-set-style "gnu") + * End: + */ diff --git a/src/plugins/wireguard/wireguard_output_tun.c b/src/plugins/wireguard/wireguard_output_tun.c new file mode 100755 index 00000000000..daec7a4a2f1 --- /dev/null +++ b/src/plugins/wireguard/wireguard_output_tun.c @@ -0,0 +1,257 @@ +/* + * Copyright (c) 2020 Doc.ai and/or its affiliates. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include <vlib/vlib.h> +#include <vnet/vnet.h> +#include <vnet/pg/pg.h> +#include <vnet/fib/ip6_fib.h> +#include <vnet/fib/ip4_fib.h> +#include <vnet/fib/fib_entry.h> +#include <vppinfra/error.h> + +#include <wireguard/wireguard.h> +#include <wireguard/wireguard_send.h> + +#define foreach_wg_output_error \ + _(NONE, "No error") \ + _(PEER, "Peer error") \ + _(KEYPAIR, "Keypair error") \ + _(HANDSHAKE_SEND, "Handshake sending failed") \ + _(TOO_BIG, "packet too big") \ + +#define WG_OUTPUT_SCRATCH_SIZE 2048 + +typedef struct wg_output_scratch_t_ +{ + u8 scratch[WG_OUTPUT_SCRATCH_SIZE]; +} wg_output_scratch_t; + +/* Cache line aligned per-thread scratch space */ +static wg_output_scratch_t *wg_output_scratchs; + +typedef enum +{ +#define _(sym,str) WG_OUTPUT_ERROR_##sym, + foreach_wg_output_error +#undef _ + WG_OUTPUT_N_ERROR, +} wg_output_error_t; + +static char *wg_output_error_strings[] = { +#define _(sym,string) string, + foreach_wg_output_error +#undef _ +}; + +typedef enum +{ + WG_OUTPUT_NEXT_ERROR, + WG_OUTPUT_NEXT_INTERFACE_OUTPUT, + WG_OUTPUT_N_NEXT, +} wg_output_next_t; + +typedef struct +{ + ip4_udp_header_t hdr; +} wg_output_tun_trace_t; + +u8 * +format_ip4_udp_header (u8 * s, va_list * args) +{ + ip4_udp_header_t *hdr = va_arg (*args, ip4_udp_header_t *); + + s = format (s, "%U:$U", + format_ip4_header, &hdr->ip4, format_udp_header, &hdr->udp); + + return (s); +} + +/* packet trace format function */ +static u8 * +format_wg_output_tun_trace (u8 * s, va_list * args) +{ + CLIB_UNUSED (vlib_main_t * vm) = va_arg (*args, vlib_main_t *); + CLIB_UNUSED (vlib_node_t * node) = va_arg (*args, vlib_node_t *); + + wg_output_tun_trace_t *t = va_arg (*args, wg_output_tun_trace_t *); + + s = format (s, "Encrypted packet: %U\n", format_ip4_udp_header, &t->hdr); + return s; +} + +VLIB_NODE_FN (wg_output_tun_node) (vlib_main_t * vm, + vlib_node_runtime_t * node, + vlib_frame_t * frame) +{ + u32 n_left_from; + u32 *from; + vlib_buffer_t *bufs[VLIB_FRAME_SIZE], **b; + u16 nexts[VLIB_FRAME_SIZE], *next; + u32 thread_index = vm->thread_index; + + from = vlib_frame_vector_args (frame); + n_left_from = frame->n_vectors; + b = bufs; + next = nexts; + + vlib_get_buffers (vm, from, bufs, n_left_from); + + wg_main_t *wmp = &wg_main; + u32 handsh_fails = 0; + wg_peer_t *peer = NULL; + + while (n_left_from > 0) + { + ip4_udp_header_t *hdr = vlib_buffer_get_current (b[0]); + u8 *plain_data = vlib_buffer_get_current (b[0]) + sizeof (ip4_header_t); + u16 plain_data_len = + clib_net_to_host_u16 (((ip4_header_t *) plain_data)->length); + + next[0] = WG_OUTPUT_NEXT_ERROR; + + peer = + wg_peer_get_by_adj_index (vnet_buffer (b[0])->ip.adj_index[VLIB_TX]); + + if (!peer || peer->is_dead) + { + b[0]->error = node->errors[WG_OUTPUT_ERROR_PEER]; + goto out; + } + + if (PREDICT_FALSE (!peer->remote.r_current)) + { + if (PREDICT_FALSE (!wg_send_handshake (vm, peer, false))) + handsh_fails++; + b[0]->error = node->errors[WG_OUTPUT_ERROR_KEYPAIR]; + goto out; + } + + size_t encrypted_packet_len = message_data_len (plain_data_len); + + /* + * Ensure there is enough space to write the encrypted data + * into the packet + */ + if (PREDICT_FALSE (encrypted_packet_len > WG_OUTPUT_SCRATCH_SIZE) || + PREDICT_FALSE ((b[0]->current_data + encrypted_packet_len) < + vlib_buffer_get_default_data_size (vm))) + { + b[0]->error = node->errors[WG_OUTPUT_ERROR_TOO_BIG]; + goto out; + } + + message_data_t *encrypted_packet = + (message_data_t *) wg_output_scratchs[thread_index].scratch; + + enum noise_state_crypt state; + state = + noise_remote_encrypt (wmp->vlib_main, + &peer->remote, + &encrypted_packet->receiver_index, + &encrypted_packet->counter, plain_data, + plain_data_len, + encrypted_packet->encrypted_data); + switch (state) + { + case SC_OK: + break; + case SC_KEEP_KEY_FRESH: + if (PREDICT_FALSE (!wg_send_handshake (vm, peer, false))) + handsh_fails++; + break; + case SC_FAILED: + //TODO: Maybe wrong + if (PREDICT_FALSE (!wg_send_handshake (vm, peer, false))) + handsh_fails++; + clib_mem_free (encrypted_packet); + goto out; + default: + break; + } + + // Here we are sure that can send packet to next node. + next[0] = WG_OUTPUT_NEXT_INTERFACE_OUTPUT; + encrypted_packet->header.type = MESSAGE_DATA; + + clib_memcpy (plain_data, (u8 *) encrypted_packet, encrypted_packet_len); + + hdr->udp.length = clib_host_to_net_u16 (encrypted_packet_len + + sizeof (udp_header_t)); + b[0]->current_length = (encrypted_packet_len + + sizeof (ip4_header_t) + sizeof (udp_header_t)); + ip4_header_set_len_w_chksum + (&hdr->ip4, clib_host_to_net_u16 (b[0]->current_length)); + + wg_timers_any_authenticated_packet_traversal (peer); + wg_timers_any_authenticated_packet_sent (peer); + wg_timers_data_sent (peer); + + out: + if (PREDICT_FALSE ((node->flags & VLIB_NODE_FLAG_TRACE) + && (b[0]->flags & VLIB_BUFFER_IS_TRACED))) + { + wg_output_tun_trace_t *t = + vlib_add_trace (vm, node, b[0], sizeof (*t)); + t->hdr = *hdr; + } + n_left_from -= 1; + next += 1; + b += 1; + } + + vlib_buffer_enqueue_to_next (vm, node, from, nexts, frame->n_vectors); + + vlib_node_increment_counter (vm, node->node_index, + WG_OUTPUT_ERROR_HANDSHAKE_SEND, handsh_fails); + + return frame->n_vectors; +} + +/* *INDENT-OFF* */ +VLIB_REGISTER_NODE (wg_output_tun_node) = +{ + .name = "wg-output-tun", + .vector_size = sizeof (u32), + .format_trace = format_wg_output_tun_trace, + .type = VLIB_NODE_TYPE_INTERNAL, + .n_errors = ARRAY_LEN (wg_output_error_strings), + .error_strings = wg_output_error_strings, + .n_next_nodes = WG_OUTPUT_N_NEXT, + .next_nodes = { + [WG_OUTPUT_NEXT_INTERFACE_OUTPUT] = "adj-midchain-tx", + [WG_OUTPUT_NEXT_ERROR] = "error-drop", + }, +}; +/* *INDENT-ON* */ + +static clib_error_t * +wireguard_output_module_init (vlib_main_t * vm) +{ + vlib_thread_main_t *tm = vlib_get_thread_main (); + + vec_validate_aligned (wg_output_scratchs, tm->n_vlib_mains, + CLIB_CACHE_LINE_BYTES); + return (NULL); +} + +VLIB_INIT_FUNCTION (wireguard_output_module_init); + +/* + * fd.io coding-style-patch-verification: ON + * + * Local Variables: + * eval: (c-set-style "gnu") + * End: + */ diff --git a/src/plugins/wireguard/wireguard_peer.c b/src/plugins/wireguard/wireguard_peer.c new file mode 100755 index 00000000000..0dcc4e20e41 --- /dev/null +++ b/src/plugins/wireguard/wireguard_peer.c @@ -0,0 +1,418 @@ +/* + * Copyright (c) 2020 Doc.ai and/or its affiliates. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include <vnet/adj/adj_midchain.h> +#include <vnet/fib/fib_table.h> +#include <wireguard/wireguard_peer.h> +#include <wireguard/wireguard_if.h> +#include <wireguard/wireguard_messages.h> +#include <wireguard/wireguard_key.h> +#include <wireguard/wireguard_send.h> +#include <wireguard/wireguard.h> + +static fib_source_t wg_fib_source; + +index_t *wg_peer_by_adj_index; + +wg_peer_t * +wg_peer_get (index_t peeri) +{ + return (pool_elt_at_index (wg_main.peers, peeri)); +} + +static void +wg_peer_endpoint_reset (wg_peer_endpoint_t * ep) +{ + ip46_address_reset (&ep->addr); + ep->port = 0; +} + +static void +wg_peer_endpoint_init (wg_peer_endpoint_t * ep, + const ip46_address_t * addr, u16 port) +{ + ip46_address_copy (&ep->addr, addr); + ep->port = port; +} + +static void +wg_peer_fib_flush (wg_peer_t * peer) +{ + wg_peer_allowed_ip_t *allowed_ip; + + vec_foreach (allowed_ip, peer->allowed_ips) + { + fib_table_entry_delete_index (allowed_ip->fib_entry_index, wg_fib_source); + allowed_ip->fib_entry_index = FIB_NODE_INDEX_INVALID; + } +} + +static void +wg_peer_fib_populate (wg_peer_t * peer, u32 fib_index) +{ + wg_peer_allowed_ip_t *allowed_ip; + + vec_foreach (allowed_ip, peer->allowed_ips) + { + allowed_ip->fib_entry_index = + fib_table_entry_path_add (fib_index, + &allowed_ip->prefix, + wg_fib_source, + FIB_ENTRY_FLAG_NONE, + fib_proto_to_dpo (allowed_ip-> + prefix.fp_proto), + &peer->dst.addr, peer->wg_sw_if_index, ~0, 1, + NULL, FIB_ROUTE_PATH_FLAG_NONE); + } +} + +static void +wg_peer_clear (vlib_main_t * vm, wg_peer_t * peer) +{ + wg_timers_stop (peer); + noise_remote_clear (vm, &peer->remote); + peer->last_sent_handshake = vlib_time_now (vm) - (REKEY_TIMEOUT + 1); + + clib_memset (&peer->cookie_maker, 0, sizeof (peer->cookie_maker)); + + wg_peer_endpoint_reset (&peer->src); + wg_peer_endpoint_reset (&peer->dst); + + if (INDEX_INVALID != peer->adj_index) + { + adj_unlock (peer->adj_index); + wg_peer_by_adj_index[peer->adj_index] = INDEX_INVALID; + } + wg_peer_fib_flush (peer); + + peer->adj_index = INDEX_INVALID; + peer->persistent_keepalive_interval = 0; + peer->timer_handshake_attempts = 0; + peer->timer_need_another_keepalive = false; + peer->is_dead = true; + vec_free (peer->allowed_ips); +} + +static void +wg_peer_init (vlib_main_t * vm, wg_peer_t * peer) +{ + wg_timers_init (peer, vlib_time_now (vm)); + wg_peer_clear (vm, peer); +} + +static u8 * +wg_peer_build_rewrite (const wg_peer_t * peer) +{ + // v4 only for now + ip4_udp_header_t *hdr; + u8 *rewrite = NULL; + + vec_validate (rewrite, sizeof (*hdr) - 1); + hdr = (ip4_udp_header_t *) rewrite; + + hdr->ip4.ip_version_and_header_length = 0x45; + hdr->ip4.ttl = 64; + hdr->ip4.src_address = peer->src.addr.ip4; + hdr->ip4.dst_address = peer->dst.addr.ip4; + hdr->ip4.protocol = IP_PROTOCOL_UDP; + hdr->ip4.checksum = ip4_header_checksum (&hdr->ip4); + + hdr->udp.src_port = clib_host_to_net_u16 (peer->src.port); + hdr->udp.dst_port = clib_host_to_net_u16 (peer->dst.port); + hdr->udp.checksum = 0; + + return (rewrite); +} + +static void +wg_peer_adj_stack (wg_peer_t * peer) +{ + ip_adjacency_t *adj; + u32 sw_if_index; + wg_if_t *wgi; + + adj = adj_get (peer->adj_index); + sw_if_index = adj->rewrite_header.sw_if_index; + + wgi = wg_if_get (wg_if_find_by_sw_if_index (sw_if_index)); + + if (!wgi) + return; + + if (!vnet_sw_interface_is_admin_up (vnet_get_main (), wgi->sw_if_index)) + { + adj_midchain_delegate_unstack (peer->adj_index); + } + else + { + /* *INDENT-OFF* */ + fib_prefix_t dst = { + .fp_len = 32, + .fp_proto = FIB_PROTOCOL_IP4, + .fp_addr = peer->dst.addr, + }; + /* *INDENT-ON* */ + u32 fib_index; + + fib_index = fib_table_find (FIB_PROTOCOL_IP4, peer->table_id); + + adj_midchain_delegate_stack (peer->adj_index, fib_index, &dst); + } +} + +walk_rc_t +wg_peer_if_admin_state_change (wg_if_t * wgi, index_t peeri, void *data) +{ + wg_peer_adj_stack (wg_peer_get (peeri)); + + return (WALK_CONTINUE); +} + +walk_rc_t +wg_peer_if_table_change (wg_if_t * wgi, index_t peeri, void *data) +{ + wg_peer_table_bind_ctx_t *ctx = data; + wg_peer_t *peer; + + peer = wg_peer_get (peeri); + + wg_peer_fib_flush (peer); + wg_peer_fib_populate (peer, ctx->new_fib_index); + + return (WALK_CONTINUE); +} + +static int +wg_peer_fill (vlib_main_t * vm, wg_peer_t * peer, + u32 table_id, + const ip46_address_t * dst, + u16 port, + u16 persistent_keepalive_interval, + const fib_prefix_t * allowed_ips, u32 wg_sw_if_index) +{ + wg_peer_endpoint_init (&peer->dst, dst, port); + + peer->table_id = table_id; + peer->persistent_keepalive_interval = persistent_keepalive_interval; + peer->wg_sw_if_index = wg_sw_if_index; + peer->last_sent_handshake = vlib_time_now (vm) - (REKEY_TIMEOUT + 1); + peer->is_dead = false; + + const wg_if_t *wgi = wg_if_get (wg_if_find_by_sw_if_index (wg_sw_if_index)); + + if (NULL == wgi) + return (VNET_API_ERROR_INVALID_INTERFACE); + + ip_address_to_46 (&wgi->src_ip, &peer->src.addr); + peer->src.port = wgi->port; + + /* + * and an adjacency for the endpoint address in the overlay + * on the wg interface + */ + peer->rewrite = wg_peer_build_rewrite (peer); + + peer->adj_index = adj_nbr_add_or_lock (FIB_PROTOCOL_IP4, + VNET_LINK_IP4, + &peer->dst.addr, wgi->sw_if_index); + + vec_validate_init_empty (wg_peer_by_adj_index, + peer->adj_index, INDEX_INVALID); + wg_peer_by_adj_index[peer->adj_index] = peer - wg_main.peers; + + adj_nbr_midchain_update_rewrite (peer->adj_index, + NULL, + NULL, + ADJ_FLAG_MIDCHAIN_IP_STACK, + vec_dup (peer->rewrite)); + wg_peer_adj_stack (peer); + + /* + * add a route in the overlay to each of the allowed-ips + */ + u32 ii; + + vec_validate (peer->allowed_ips, vec_len (allowed_ips) - 1); + + vec_foreach_index (ii, allowed_ips) + { + peer->allowed_ips[ii].prefix = allowed_ips[ii]; + } + + wg_peer_fib_populate (peer, + fib_table_get_index_for_sw_if_index + (FIB_PROTOCOL_IP4, peer->wg_sw_if_index)); + + return (0); +} + +int +wg_peer_add (u32 tun_sw_if_index, + const u8 public_key[NOISE_PUBLIC_KEY_LEN], + u32 table_id, + const ip46_address_t * endpoint, + const fib_prefix_t * allowed_ips, + u16 port, u16 persistent_keepalive, u32 * peer_index) +{ + wg_if_t *wg_if; + wg_peer_t *peer; + int rv; + + vlib_main_t *vm = vlib_get_main (); + + if (tun_sw_if_index == ~0) + return (VNET_API_ERROR_INVALID_SW_IF_INDEX); + + wg_if = wg_if_get (wg_if_find_by_sw_if_index (tun_sw_if_index)); + if (!wg_if) + return (VNET_API_ERROR_INVALID_SW_IF_INDEX); + + /* *INDENT-OFF* */ + pool_foreach (peer, wg_main.peers, + ({ + if (!memcmp (peer->remote.r_public, public_key, NOISE_PUBLIC_KEY_LEN)) + { + return (VNET_API_ERROR_ENTRY_ALREADY_EXISTS); + } + })); + /* *INDENT-ON* */ + + if (pool_elts (wg_main.peers) > MAX_PEERS) + return (VNET_API_ERROR_LIMIT_EXCEEDED); + + pool_get (wg_main.peers, peer); + + wg_peer_init (vm, peer); + + rv = wg_peer_fill (vm, peer, table_id, endpoint, (u16) port, + persistent_keepalive, allowed_ips, tun_sw_if_index); + + if (rv) + { + wg_peer_clear (vm, peer); + pool_put (wg_main.peers, peer); + return (rv); + } + + noise_remote_init (&peer->remote, peer - wg_main.peers, public_key, + &wg_if->local); + cookie_maker_init (&peer->cookie_maker, public_key); + + if (peer->persistent_keepalive_interval != 0) + { + wg_send_keepalive (vm, peer); + } + + *peer_index = peer - wg_main.peers; + wg_if_peer_add (wg_if, *peer_index); + + return (0); +} + +int +wg_peer_remove (index_t peeri) +{ + wg_main_t *wmp = &wg_main; + wg_peer_t *peer = NULL; + wg_if_t *wgi; + + if (pool_is_free_index (wmp->peers, peeri)) + return VNET_API_ERROR_NO_SUCH_ENTRY; + + peer = pool_elt_at_index (wmp->peers, peeri); + + wgi = wg_if_get (wg_if_find_by_sw_if_index (peer->wg_sw_if_index)); + wg_if_peer_remove (wgi, peeri); + + vnet_feature_enable_disable ("ip4-output", "wg-output-tun", + peer->wg_sw_if_index, 0, 0, 0); + wg_peer_clear (wmp->vlib_main, peer); + pool_put (wmp->peers, peer); + + return (0); +} + +void +wg_peer_walk (wg_peer_walk_cb_t fn, void *data) +{ + index_t peeri; + + /* *INDENT-OFF* */ + pool_foreach_index(peeri, wg_main.peers, + { + if (WALK_STOP == fn(peeri, data)) + break; + }); + /* *INDENT-ON* */ +} + +static u8 * +format_wg_peer_endpoint (u8 * s, va_list * args) +{ + wg_peer_endpoint_t *ep = va_arg (*args, wg_peer_endpoint_t *); + + s = format (s, "%U:%d", + format_ip46_address, &ep->addr, IP46_TYPE_ANY, ep->port); + + return (s); +} + +u8 * +format_wg_peer (u8 * s, va_list * va) +{ + index_t peeri = va_arg (*va, index_t); + wg_peer_allowed_ip_t *allowed_ip; + u8 key[NOISE_KEY_LEN_BASE64]; + wg_peer_t *peer; + + peer = wg_peer_get (peeri); + key_to_base64 (peer->remote.r_public, NOISE_PUBLIC_KEY_LEN, key); + + s = format (s, "[%d] key:%=45s endpoint:[%U->%U] %U keep-alive:%d adj:%d", + peeri, + key, + format_wg_peer_endpoint, &peer->src, + format_wg_peer_endpoint, &peer->dst, + format_vnet_sw_if_index_name, vnet_get_main (), + peer->wg_sw_if_index, + peer->persistent_keepalive_interval, peer->adj_index); + + s = format (s, "\n allowed-ips:"); + vec_foreach (allowed_ip, peer->allowed_ips) + { + s = format (s, " %U", format_fib_prefix, &allowed_ip->prefix); + } + + return s; +} + +static clib_error_t * +wg_peer_module_init (vlib_main_t * vm) +{ + wg_fib_source = fib_source_allocate ("wireguard", 0xb0, // + FIB_SOURCE_BH_SIMPLE); + + return (NULL); +} + +VLIB_INIT_FUNCTION (wg_peer_module_init); + +/* + * fd.io coding-style-patch-verification: ON + * + * Local Variables: + * eval: (c-set-style "gnu") + * End: + */ diff --git a/src/plugins/wireguard/wireguard_peer.h b/src/plugins/wireguard/wireguard_peer.h new file mode 100755 index 00000000000..99c73f3a0ed --- /dev/null +++ b/src/plugins/wireguard/wireguard_peer.h @@ -0,0 +1,122 @@ +/* + * Copyright (c) 2020 Doc.ai and/or its affiliates. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef __included_wg_peer_h__ +#define __included_wg_peer_h__ + +#include <vnet/ip/ip.h> + +#include <wireguard/wireguard_cookie.h> +#include <wireguard/wireguard_timer.h> +#include <wireguard/wireguard_key.h> +#include <wireguard/wireguard_messages.h> +#include <wireguard/wireguard_if.h> + +typedef struct ip4_udp_header_t_ +{ + ip4_header_t ip4; + udp_header_t udp; +} __clib_packed ip4_udp_header_t; + +u8 *format_ip4_udp_header (u8 * s, va_list * va); + +typedef struct wg_peer_allowed_ip_t_ +{ + fib_prefix_t prefix; + fib_node_index_t fib_entry_index; +} wg_peer_allowed_ip_t; + +typedef struct wg_peer_endpoint_t_ +{ + ip46_address_t addr; + u16 port; +} wg_peer_endpoint_t; + +typedef struct wg_peer +{ + noise_remote_t remote; + cookie_maker_t cookie_maker; + + /* Peer addresses */ + wg_peer_endpoint_t dst; + wg_peer_endpoint_t src; + u32 table_id; + adj_index_t adj_index; + + /* rewrite built from address information */ + u8 *rewrite; + + /* Vector of allowed-ips */ + wg_peer_allowed_ip_t *allowed_ips; + + /* The WG interface this peer is attached to */ + u32 wg_sw_if_index; + + /* Timers */ + tw_timer_wheel_16t_2w_512sl_t timer_wheel; + u32 timers[WG_N_TIMERS]; + u32 timer_handshake_attempts; + u16 persistent_keepalive_interval; + f64 last_sent_handshake; + bool timer_need_another_keepalive; + + bool is_dead; +} wg_peer_t; + +typedef struct wg_peer_table_bind_ctx_t_ +{ + ip_address_family_t af; + u32 new_fib_index; + u32 old_fib_index; +} wg_peer_table_bind_ctx_t; + +int wg_peer_add (u32 tun_sw_if_index, + const u8 public_key_64[NOISE_PUBLIC_KEY_LEN], + u32 table_id, + const ip46_address_t * endpoint, + const fib_prefix_t * allowed_ips, + u16 port, u16 persistent_keepalive, index_t * peer_index); +int wg_peer_remove (u32 peer_index); + +typedef walk_rc_t (*wg_peer_walk_cb_t) (index_t peeri, void *arg); +void wg_peer_walk (wg_peer_walk_cb_t fn, void *data); + +u8 *format_wg_peer (u8 * s, va_list * va); +wg_peer_t *wg_peer_get (index_t peeri); + +walk_rc_t wg_peer_if_admin_state_change (wg_if_t * wgi, index_t peeri, + void *data); +walk_rc_t wg_peer_if_table_change (wg_if_t * wgi, index_t peeri, void *data); + +/* + * Expoed for the data-plane + */ +extern index_t *wg_peer_by_adj_index; + +static inline wg_peer_t * +wg_peer_get_by_adj_index (index_t ai) +{ + return wg_peer_get (wg_peer_by_adj_index[ai]); +} + +#endif // __included_wg_peer_h__ + +/* + * fd.io coding-style-patch-verification: ON + * + * Local Variables: + * eval: (c-set-style "gnu") + * End: + */ diff --git a/src/plugins/wireguard/wireguard_send.c b/src/plugins/wireguard/wireguard_send.c new file mode 100755 index 00000000000..a5d8aaf6900 --- /dev/null +++ b/src/plugins/wireguard/wireguard_send.c @@ -0,0 +1,225 @@ +/* + * Copyright (c) 2020 Doc.ai and/or its affiliates. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include <vnet/vnet.h> +#include <vnet/fib/ip6_fib.h> +#include <vnet/fib/ip4_fib.h> +#include <vnet/fib/fib_entry.h> +#include <vnet/ip/ip6_link.h> +#include <vnet/pg/pg.h> +#include <vnet/udp/udp.h> +#include <vppinfra/error.h> +#include <wireguard/wireguard.h> +#include <wireguard/wireguard_send.h> + +static int +ip46_enqueue_packet (vlib_main_t * vm, u32 bi0, int is_ip6) +{ + vlib_frame_t *f = 0; + u32 lookup_node_index = + is_ip6 ? ip6_lookup_node.index : ip4_lookup_node.index; + + f = vlib_get_frame_to_node (vm, lookup_node_index); + /* f can not be NULL here - frame allocation failure causes panic */ + + u32 *to_next = vlib_frame_vector_args (f); + f->n_vectors = 1; + to_next[0] = bi0; + + vlib_put_frame_to_node (vm, lookup_node_index, f); + + return f->n_vectors; +} + +static void +wg_buffer_prepend_rewrite (vlib_buffer_t * b0, const wg_peer_t * peer) +{ + ip4_udp_header_t *hdr; + + vlib_buffer_advance (b0, -sizeof (*hdr)); + + hdr = vlib_buffer_get_current (b0); + clib_memcpy (hdr, peer->rewrite, vec_len (peer->rewrite)); + + hdr->udp.length = + clib_host_to_net_u16 (b0->current_length - sizeof (ip4_header_t)); + ip4_header_set_len_w_chksum (&hdr->ip4, + clib_host_to_net_u16 (b0->current_length)); +} + +static bool +wg_create_buffer (vlib_main_t * vm, + const wg_peer_t * peer, + const u8 * packet, u32 packet_len, u32 * bi) +{ + u32 n_buf0 = 0; + vlib_buffer_t *b0; + + n_buf0 = vlib_buffer_alloc (vm, bi, 1); + if (!n_buf0) + return false; + + b0 = vlib_get_buffer (vm, *bi); + + u8 *payload = vlib_buffer_get_current (b0); + clib_memcpy (payload, packet, packet_len); + + b0->current_length = packet_len; + + wg_buffer_prepend_rewrite (b0, peer); + + return true; +} + +bool +wg_send_handshake (vlib_main_t * vm, wg_peer_t * peer, bool is_retry) +{ + wg_main_t *wmp = &wg_main; + message_handshake_initiation_t packet; + + if (!is_retry) + peer->timer_handshake_attempts = 0; + + if (!wg_birthdate_has_expired (peer->last_sent_handshake, + REKEY_TIMEOUT) || peer->is_dead) + { + return true; + } + if (noise_create_initiation (wmp->vlib_main, + &peer->remote, + &packet.sender_index, + packet.unencrypted_ephemeral, + packet.encrypted_static, + packet.encrypted_timestamp)) + { + f64 now = vlib_time_now (vm); + packet.header.type = MESSAGE_HANDSHAKE_INITIATION; + cookie_maker_mac (&peer->cookie_maker, &packet.macs, &packet, + sizeof (packet)); + wg_timers_any_authenticated_packet_traversal (peer); + wg_timers_any_authenticated_packet_sent (peer); + peer->last_sent_handshake = now; + wg_timers_handshake_initiated (peer); + } + else + return false; + u32 bi0 = 0; + if (!wg_create_buffer (vm, peer, (u8 *) & packet, sizeof (packet), &bi0)) + return false; + ip46_enqueue_packet (vm, bi0, false); + + return true; +} + +bool +wg_send_keepalive (vlib_main_t * vm, wg_peer_t * peer) +{ + wg_main_t *wmp = &wg_main; + u32 size_of_packet = message_data_len (0); + message_data_t *packet = clib_mem_alloc (size_of_packet); + u32 bi0 = 0; + bool ret = true; + enum noise_state_crypt state; + + if (!peer->remote.r_current) + { + wg_send_handshake (vm, peer, false); + goto out; + } + + state = + noise_remote_encrypt (wmp->vlib_main, + &peer->remote, + &packet->receiver_index, + &packet->counter, NULL, 0, packet->encrypted_data); + switch (state) + { + case SC_OK: + break; + case SC_KEEP_KEY_FRESH: + wg_send_handshake (vm, peer, false); + break; + case SC_FAILED: + ret = false; + goto out; + default: + break; + } + packet->header.type = MESSAGE_DATA; + + if (!wg_create_buffer (vm, peer, (u8 *) packet, size_of_packet, &bi0)) + { + ret = false; + goto out; + } + + ip46_enqueue_packet (vm, bi0, false); + wg_timers_any_authenticated_packet_traversal (peer); + wg_timers_any_authenticated_packet_sent (peer); + +out: + clib_mem_free (packet); + return ret; +} + +bool +wg_send_handshake_response (vlib_main_t * vm, wg_peer_t * peer) +{ + wg_main_t *wmp = &wg_main; + message_handshake_response_t packet; + + peer->last_sent_handshake = vlib_time_now (vm); + + if (noise_create_response (vm, + &peer->remote, + &packet.sender_index, + &packet.receiver_index, + packet.unencrypted_ephemeral, + packet.encrypted_nothing)) + { + f64 now = vlib_time_now (vm); + packet.header.type = MESSAGE_HANDSHAKE_RESPONSE; + cookie_maker_mac (&peer->cookie_maker, &packet.macs, &packet, + sizeof (packet)); + + if (noise_remote_begin_session (wmp->vlib_main, &peer->remote)) + { + wg_timers_session_derived (peer); + wg_timers_any_authenticated_packet_traversal (peer); + wg_timers_any_authenticated_packet_sent (peer); + peer->last_sent_handshake = now; + + u32 bi0 = 0; + if (!wg_create_buffer (vm, peer, (u8 *) & packet, + sizeof (packet), &bi0)) + return false; + + ip46_enqueue_packet (vm, bi0, false); + } + else + return false; + } + else + return false; + return true; +} + +/* + * fd.io coding-style-patch-verification: ON + * + * Local Variables: + * eval: (c-set-style "gnu") + * End: + */ diff --git a/src/plugins/wireguard/wireguard_send.h b/src/plugins/wireguard/wireguard_send.h new file mode 100755 index 00000000000..8f5e7ab8765 --- /dev/null +++ b/src/plugins/wireguard/wireguard_send.h @@ -0,0 +1,45 @@ +/* + * Copyright (c) 2020 Doc.ai and/or its affiliates. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef __included_wg_send_h__ +#define __included_wg_send_h__ + +#include <wireguard/wireguard_peer.h> + +bool wg_send_keepalive (vlib_main_t * vm, wg_peer_t * peer); +bool wg_send_handshake (vlib_main_t * vm, wg_peer_t * peer, bool is_retry); +bool wg_send_handshake_response (vlib_main_t * vm, wg_peer_t * peer); + +always_inline void +ip4_header_set_len_w_chksum (ip4_header_t * ip4, u16 len) +{ + ip_csum_t sum = ip4->checksum; + u8 old = ip4->length; + u8 new = len; + + sum = ip_csum_update (sum, old, new, ip4_header_t, length); + ip4->checksum = ip_csum_fold (sum); + ip4->length = new; +} + +#endif /* __included_wg_send_h__ */ + +/* + * fd.io coding-style-patch-verification: ON + * + * Local Variables: + * eval: (c-set-style "gnu") + * End: + */ diff --git a/src/plugins/wireguard/wireguard_timer.c b/src/plugins/wireguard/wireguard_timer.c new file mode 100755 index 00000000000..e4d4030bb18 --- /dev/null +++ b/src/plugins/wireguard/wireguard_timer.c @@ -0,0 +1,324 @@ +/* + * Copyright (c) 2020 Doc.ai and/or its affiliates. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include <wireguard/wireguard.h> +#include <wireguard/wireguard_send.h> +#include <wireguard/wireguard_timer.h> + +static u32 +get_random_u32_max (u32 max) +{ + vlib_main_t *vm = vlib_get_main (); + u32 seed = (u32) (vlib_time_now (vm) * 1e6); + return random_u32 (&seed) % max; +} + +static void +stop_timer (wg_peer_t * peer, u32 timer_id) +{ + if (peer->timers[timer_id] != ~0) + { + tw_timer_stop_16t_2w_512sl (&peer->timer_wheel, peer->timers[timer_id]); + peer->timers[timer_id] = ~0; + } +} + +static void +start_or_update_timer (wg_peer_t * peer, u32 timer_id, u32 interval) +{ + if (peer->timers[timer_id] == ~0) + { + wg_main_t *wmp = &wg_main; + peer->timers[timer_id] = + tw_timer_start_16t_2w_512sl (&peer->timer_wheel, peer - wmp->peers, + timer_id, interval); + } + else + { + tw_timer_update_16t_2w_512sl (&peer->timer_wheel, + peer->timers[timer_id], interval); + } +} + +static void +wg_expired_retransmit_handshake (vlib_main_t * vm, wg_peer_t * peer) +{ + + if (peer->timer_handshake_attempts > MAX_TIMER_HANDSHAKES) + { + stop_timer (peer, WG_TIMER_SEND_KEEPALIVE); + + /* We set a timer for destroying any residue that might be left + * of a partial exchange. + */ + + if (peer->timers[WG_TIMER_KEY_ZEROING] == ~0) + { + wg_main_t *wmp = &wg_main; + + peer->timers[WG_TIMER_KEY_ZEROING] = + tw_timer_start_16t_2w_512sl (&peer->timer_wheel, + peer - wmp->peers, + WG_TIMER_KEY_ZEROING, + REJECT_AFTER_TIME * 3 * WHZ); + } + } + else + { + ++peer->timer_handshake_attempts; + wg_send_handshake (vm, peer, true); + } +} + +static void +wg_expired_send_keepalive (vlib_main_t * vm, wg_peer_t * peer) +{ + wg_send_keepalive (vm, peer); + + if (peer->timer_need_another_keepalive) + { + peer->timer_need_another_keepalive = false; + start_or_update_timer (peer, WG_TIMER_SEND_KEEPALIVE, + KEEPALIVE_TIMEOUT * WHZ); + } +} + +static void +wg_expired_send_persistent_keepalive (vlib_main_t * vm, wg_peer_t * peer) +{ + if (peer->persistent_keepalive_interval) + { + wg_send_keepalive (vm, peer); + } +} + +static void +wg_expired_new_handshake (vlib_main_t * vm, wg_peer_t * peer) +{ + wg_send_handshake (vm, peer, false); +} + +static void +wg_expired_zero_key_material (vlib_main_t * vm, wg_peer_t * peer) +{ + if (!peer->is_dead) + { + noise_remote_clear (vm, &peer->remote); + } +} + + +void +wg_timers_any_authenticated_packet_traversal (wg_peer_t * peer) +{ + if (peer->persistent_keepalive_interval) + { + start_or_update_timer (peer, WG_TIMER_PERSISTENT_KEEPALIVE, + peer->persistent_keepalive_interval * WHZ); + } +} + +void +wg_timers_any_authenticated_packet_sent (wg_peer_t * peer) +{ + stop_timer (peer, WG_TIMER_SEND_KEEPALIVE); +} + +void +wg_timers_handshake_initiated (wg_peer_t * peer) +{ + u32 interval = + REKEY_TIMEOUT * WHZ + get_random_u32_max (REKEY_TIMEOUT_JITTER); + start_or_update_timer (peer, WG_TIMER_RETRANSMIT_HANDSHAKE, interval); +} + +void +wg_timers_session_derived (wg_peer_t * peer) +{ + start_or_update_timer (peer, WG_TIMER_KEY_ZEROING, + REJECT_AFTER_TIME * 3 * WHZ); +} + +/* Should be called after an authenticated data packet is sent. */ +void +wg_timers_data_sent (wg_peer_t * peer) +{ + u32 interval = (KEEPALIVE_TIMEOUT + REKEY_TIMEOUT) * WHZ + + get_random_u32_max (REKEY_TIMEOUT_JITTER); + + if (peer->timers[WG_TIMER_NEW_HANDSHAKE] == ~0) + { + wg_main_t *wmp = &wg_main; + peer->timers[WG_TIMER_NEW_HANDSHAKE] = + tw_timer_start_16t_2w_512sl (&peer->timer_wheel, peer - wmp->peers, + WG_TIMER_NEW_HANDSHAKE, interval); + } +} + +/* Should be called after an authenticated data packet is received. */ +void +wg_timers_data_received (wg_peer_t * peer) +{ + if (peer->timers[WG_TIMER_SEND_KEEPALIVE] == ~0) + { + wg_main_t *wmp = &wg_main; + peer->timers[WG_TIMER_SEND_KEEPALIVE] = + tw_timer_start_16t_2w_512sl (&peer->timer_wheel, peer - wmp->peers, + WG_TIMER_SEND_KEEPALIVE, + KEEPALIVE_TIMEOUT * WHZ); + } + else + { + peer->timer_need_another_keepalive = true; + } +} + +/* Should be called after a handshake response message is received and processed + * or when getting key confirmation via the first data message. + */ +void +wg_timers_handshake_complete (wg_peer_t * peer) +{ + stop_timer (peer, WG_TIMER_RETRANSMIT_HANDSHAKE); + + peer->timer_handshake_attempts = 0; +} + +void +wg_timers_any_authenticated_packet_received (wg_peer_t * peer) +{ + stop_timer (peer, WG_TIMER_NEW_HANDSHAKE); +} + +static vlib_node_registration_t wg_timer_mngr_node; + +static void +expired_timer_callback (u32 * expired_timers) +{ + int i; + u32 timer_id; + u32 pool_index; + + wg_main_t *wmp = &wg_main; + vlib_main_t *vm = wmp->vlib_main; + + wg_peer_t *peer; + + /* Need to invalidate all of them because one can restart other */ + for (i = 0; i < vec_len (expired_timers); i++) + { + pool_index = expired_timers[i] & 0x0FFFFFFF; + timer_id = expired_timers[i] >> 28; + + peer = pool_elt_at_index (wmp->peers, pool_index); + peer->timers[timer_id] = ~0; + } + + for (i = 0; i < vec_len (expired_timers); i++) + { + pool_index = expired_timers[i] & 0x0FFFFFFF; + timer_id = expired_timers[i] >> 28; + + peer = pool_elt_at_index (wmp->peers, pool_index); + switch (timer_id) + { + case WG_TIMER_RETRANSMIT_HANDSHAKE: + wg_expired_retransmit_handshake (vm, peer); + break; + case WG_TIMER_PERSISTENT_KEEPALIVE: + wg_expired_send_persistent_keepalive (vm, peer); + break; + case WG_TIMER_SEND_KEEPALIVE: + wg_expired_send_keepalive (vm, peer); + break; + case WG_TIMER_NEW_HANDSHAKE: + wg_expired_new_handshake (vm, peer); + break; + case WG_TIMER_KEY_ZEROING: + wg_expired_zero_key_material (vm, peer); + break; + default: + break; + } + } +} + +void +wg_timers_init (wg_peer_t * peer, f64 now) +{ + for (int i = 0; i < WG_N_TIMERS; i++) + { + peer->timers[i] = ~0; + } + tw_timer_wheel_16t_2w_512sl_t *tw = &peer->timer_wheel; + tw_timer_wheel_init_16t_2w_512sl (tw, + expired_timer_callback, + WG_TICK /* timer period in s */ , ~0); + tw->last_run_time = now; + peer->adj_index = INDEX_INVALID; +} + +static uword +wg_timer_mngr_fn (vlib_main_t * vm, vlib_node_runtime_t * rt, + vlib_frame_t * f) +{ + wg_main_t *wmp = &wg_main; + wg_peer_t *peers; + wg_peer_t *peer; + + while (1) + { + vlib_process_wait_for_event_or_clock (vm, WG_TICK); + vlib_process_get_events (vm, NULL); + + peers = wmp->peers; + /* *INDENT-OFF* */ + pool_foreach (peer, peers, + ({ + tw_timer_expire_timers_16t_2w_512sl + (&peer->timer_wheel, vlib_time_now (vm)); + })); + /* *INDENT-ON* */ + } + + return 0; +} + +void +wg_timers_stop (wg_peer_t * peer) +{ + stop_timer (peer, WG_TIMER_RETRANSMIT_HANDSHAKE); + stop_timer (peer, WG_TIMER_PERSISTENT_KEEPALIVE); + stop_timer (peer, WG_TIMER_SEND_KEEPALIVE); + stop_timer (peer, WG_TIMER_NEW_HANDSHAKE); + stop_timer (peer, WG_TIMER_KEY_ZEROING); +} + +/* *INDENT-OFF* */ +VLIB_REGISTER_NODE (wg_timer_mngr_node, static) = { + .function = wg_timer_mngr_fn, + .type = VLIB_NODE_TYPE_PROCESS, + .name = + "wg-timer-manager", +}; +/* *INDENT-ON* */ + +/* + * fd.io coding-style-patch-verification: ON + * + * Local Variables: + * eval: (c-set-style "gnu") + * End: + */ diff --git a/src/plugins/wireguard/wireguard_timer.h b/src/plugins/wireguard/wireguard_timer.h new file mode 100755 index 00000000000..457dce28674 --- /dev/null +++ b/src/plugins/wireguard/wireguard_timer.h @@ -0,0 +1,69 @@ +/* + * Copyright (c) 2020 Doc.ai and/or its affiliates. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef __included_wg_timer_h__ +#define __included_wg_timer_h__ + +#include <vlib/vlib.h> +#include <vppinfra/clib.h> +#include <vppinfra/tw_timer_16t_2w_512sl.h> + +/** WG timers */ +#define foreach_wg_timer \ + _(RETRANSMIT_HANDSHAKE, "RETRANSMIT HANDSHAKE") \ + _(PERSISTENT_KEEPALIVE, "PERSISTENT KEEPALIVE") \ + _(SEND_KEEPALIVE, "SEND KEEPALIVE") \ + _(NEW_HANDSHAKE, "NEW HANDSHAKE") \ + _(KEY_ZEROING, "KEY ZEROING") \ + +typedef enum _wg_timers +{ +#define _(sym, str) WG_TIMER_##sym, + foreach_wg_timer +#undef _ + WG_N_TIMERS +} wg_timers_e; + +typedef struct wg_peer wg_peer_t; + +void wg_timers_init (wg_peer_t * peer, f64 now); +void wg_timers_stop (wg_peer_t * peer); +void wg_timers_data_sent (wg_peer_t * peer); +void wg_timers_data_received (wg_peer_t * peer); +void wg_timers_any_authenticated_packet_sent (wg_peer_t * peer); +void wg_timers_any_authenticated_packet_received (wg_peer_t * peer); +void wg_timers_handshake_initiated (wg_peer_t * peer); +void wg_timers_handshake_complete (wg_peer_t * peer); +void wg_timers_session_derived (wg_peer_t * peer); +void wg_timers_any_authenticated_packet_traversal (wg_peer_t * peer); + + +static inline bool +wg_birthdate_has_expired (f64 birthday_seconds, f64 expiration_seconds) +{ + f64 now_seconds = vlib_time_now (vlib_get_main ()); + return (birthday_seconds + expiration_seconds) < now_seconds; +} + + +#endif /* __included_wg_timer_h__ */ + +/* + * fd.io coding-style-patch-verification: ON + * + * Local Variables: + * eval: (c-set-style "gnu") + * End: + */ diff --git a/test/requirements-3.txt b/test/requirements-3.txt index a17c49fc3ac..f955f37c471 100644 --- a/test/requirements-3.txt +++ b/test/requirements-3.txt @@ -74,7 +74,7 @@ cryptography==2.9.2 \ --hash=sha256:daf54a4b07d67ad437ff239c8a4080cfd1cc7213df57d33c97de7b4738048d5e \ --hash=sha256:e993468c859d084d5579e2ebee101de8f5a27ce8e2159959b6673b418fd8c785 \ --hash=sha256:f118a95c7480f5be0df8afeb9a11bd199aa20afab7a96bcf20409b411a3a85f0 \ - # via -r requirements.txt + # via -r requirements.txt, noiseprotocol deprecation==2.1.0 \ --hash=sha256:72b3bde64e5d778694b0cf68178aed03d15e15477116add3fb773e581f9518ff \ --hash=sha256:a10811591210e1fb0e768a8c25517cabeabcba6f0bf96564f8ff45189f90b14a \ @@ -149,6 +149,9 @@ mccabe==0.6.1 \ --hash=sha256:ab8a6258860da4b6677da4bd2fe5dc2c659cff31b3ee4f7f5d64e79735b80d42 \ --hash=sha256:dd8d182285a0fe56bace7f45b5e7d1a6ebcbf524e8f3bd87eb0f125271b8831f \ # via flake8 +noiseprotocol==0.3.1 \ + --hash=sha256:2e1a603a38439636cf0ffd8b3e8b12cee27d368a28b41be7dbe568b2abb23111 \ + # via -r requirements.txt objgraph==3.4.1 \ --hash=sha256:10d50116d1a66934d143a07308a5b3b3edcc021e1457f66ff792584166cae7cd \ --hash=sha256:bf29512d7f8b457b53fa0722ea59f516abb8abc59b78f97f0ef81394a0c615a7 \ diff --git a/test/requirements.txt b/test/requirements.txt index 5f9f44c2244..e934bc042a2 100644 --- a/test/requirements.txt +++ b/test/requirements.txt @@ -19,3 +19,4 @@ objgraph # MIT pympler # Apache-2.0 sphinx<3.0.0 # BSD, sphinx 3.0.0 crashes with a traceback sphinx-rtd-theme # MIT +noiseprotocol # MIT |