diff --git a/README.bgpsec b/README.bgpsec new file mode 100644 index 00000000..283ba075 --- /dev/null +++ b/README.bgpsec @@ -0,0 +1,275 @@ + + 1. BGPSEC tar ball + 2. Installation Instructions: + 3. BIRD run time configuration + 4. Getting RPKI-RTR data (ROA's and Router Keys) + 5. License(s) + + +1. BGPSEC patch + +This code adds BGPSEC capability to the BIRD BGP implementation. + +This has only been tested on Linux machines. It is in an Alpha release +and ***should not be considered for production systems***. The basic +BGPSEC protocol is supported with a several notable exceptions: more +than one signature block (for algorithm rollover), confederations, and +bugs we have not seen yet. + +For information on BGPSEC see the Internet Engineering Task Force +(IETF) Secure Inter-Domain Routing (SIDR) working group page and +specifically the draft describing the BGPSEC protocol: + +https://datatracker.ietf.org/wg/sidr/ +https://datatracker.ietf.org/doc/draft-ietf-sidr-bgpsec-protocol/ + +This code is based on the v1.5.0 of the BIRD software. Information +about BIRD including download instructions can be found at: + +http://bird.network.cz/ + + +2. Installation Instructions: + +General Instructions + +Building BGPSEC enabled bird + +This describes building bird with BGPSEC support turned on, which +requires a few steps. Contents + + 2.1 Dependencies + 2.1.1 Use An OpenSSL version that supports ECDSA (Elliptic + Curve Digital Signature Algorithm) + 2.2 Building Bird + 2.2.1 Configuring and Compiling + 2.3 Testing + 2.4 Using It + 2.5 Coding For It + + +2.1 Dependencies + +On Fedora, you'll want flex, bison, and readline-devel packages. + +2.1.1 Use an OpenSSL version that supports ECDSA (Elliptic Curve + Digital Signature Algorithm) + +The default OpenSSL distributed on some Linux vendors does not include +elliptic curve support. If yours distribution does not support +elliptic curve in the OpenSSL libraries, you'll need to grab a fresh +copy and compile it by hand. You may want to install it in a location +separate from the normally installed package. Use the --prefix option +to do this: + + # ./config --prefix=/usr/local/openssl-ecdsa + +Then make and make install + + +2.2 Building Bird + +Configuring and Compiling + +If you are using the patch, download BIRD bird-1.4.5.tar.gz from +http://bird.network.cz/ + + # tar xvjpf bird-1.5.0.-bgpsec-0.7.tar.bz2 + # cd bird-1.5.0-bgpsec-0.7/ + +Build it. +First rebuild configure (configure.in was changed by the patch): + + # autoconf + +Then Use configure flags that look something like the following. if a +version of OpenSSL that supported ecdsa had to be installed in a +non-standard location on your platform, it will be necessary to add +something like '-I/path/to//openssl-ecdsa/include' and +'-L/path/to/openssl-ecdsa/lib' options to the configure command. + + # ./configure '--enable-bgpsec' + +Then make and you should be good to go. + + +2.3 Using It + +You can create key pairs using the proto/bgp/bgpsec/keytool.py +script. For Example: + +# proto/bgp/bgpsec/keytool.py --printski --public-key-dir /usr/share/bird/bgpsec-keys --private-key-dir /usr/share/bird/bgpsec-private-keys generate 'ASN' + 40C70252FE48D29401E9156ADBECF3EF42296AE4 + +Where ASN is the AS number for the key you are generating. + +The generated public key is stored in '--public-key-dir' (default +/usr/share/bird/bgpsec-keys) and the private key is stored in +'--private-key-dir' (default /usr/share/bird/bgpsec-private-keys). +The file names are based on the AS number and the SKI value associated +with the keys, 'ASN.SKI#', e.g. for an ASN of 12345, +12345.40C70252FE48D29401E9156ADBECF3EF42296AE4. + +The public key can be copied to other machines and placed in the same +public key directory without the private key. Likewise, keys from +other routers can be placed into the public key directory with their +ASN/SKI identifying the file names in order for the validation +routines to look them up. + +NOTE: in the future, the rpki-rtr protocol could be used instead to +pull router keys. For example, BGPSEC-BIRD-Client is a tool that can +pull router keys from a rpki cache using the rpki-rtr protocol. + + +2.4 Coding For It + +The API for use in validating stuff can be found in +proto/bgp/bgpsec/validate.h. But most importantly, these two functions +will be of the most use: + + int bgpsec_sign_data_with_ski(...); + int bgpsec_verify_signature_with_ski(...); + +As they sign and verify data simply by passing the data along with a +SKI in ascii/hex form and a ASN integer (in reality, it's just the +filename from above so as long as it can be stored in a file name it's +usable). + +The algorithm option should be set to +BGPSEC_ALGORITHM_SHA256_ECDSA_P_256 or BGPSEC_DEFAULT_CURVE. + + +3. BIRD run time configuration + +The BGPSEC implementation currently has several additional +configuration options for the configuration file. The following is an +example bgp section from a BIRD configuration file supporting BGPSEC: + + protocol bgp { + # BGPsec configuration + + # AS4 is required for BGPSEC, this must be enabled + enable as4; + + # enable bgpsec for this connection + bgpsec on; + + # The local BIRD router subject key identifier (SKI) for this + # connection. 'bgpsec_ski' identifies the (private) key that + # the local BIRD router should use to sign BGPSEC packets on + # this connection. + bgpsec_ski "8CA56CF0A4D943ACCEB9CB67967561CA8A773B73" ; + + # The local directory paths for the public router key and private + # key storage. The defaults are below: + + bgpsec_key_repo_path "/usr/share/bird/bgpsec-keys/" ; + bgpsec_priv_key_path "/usr/share/bird/bgpsec-private-keys" ; + + # bgpsec_no_pcount0 indicates whether a peer is allowed to + # set its pcount to 0. Default is true. Set this value to + # false/0 if you want to allow your peer to not have their AS + # included in the effective AS_PATH of a route (e.g. Route + # Servers). + bgpsec_no_pcount0 1; + + # bgpsec_prefer indicates whether validly signed bgpsec + # routes are preferred to non-valid and/or non-signed + # routes. Default is true. This decision is made after the + # local pref and before the as_path comparison in the best + # route selection algorithm. + bgpsec_prefer 1; + + # bgpsec_require indicates whether bgpsec signed routes are + # required on this connection. If true, Non-signed routes + # will not be accepted. Default is false. + bgpsec_require 0; + + # bgpsec_no_invalid_routes indicates if invalid routes are + # accepted. If true, routes that fail the BGPsec validity + # check are not accepted. Default is false. + bgpsec_no_invalid_routes 0; + + + # Non BGPsec configuration + + description "BGP Link"; + local as 64521; + + neighbor 172.16.1.2 as 64522; + gateway direct; + + path metric 1; # prefer shorter paths + default bgp_med 0; # when none is available + + password "demonet"; + } + + +4. Getting RPKI-RTR data (ROA's and Router Keys) + +BGPSEC-BIRD-client is a separate application that is provided in order +to pull data from a rpki-rtr using rtrLib. It can garner Router +Origin Authorizations (ROAs) from a rpki-rtr and populate BIRD's ROA +tables in order to filter for Origin Authentication. It can get +router public keys and place them in the local file system for use by +the BGPsec code. Please see the README with that software for +instructions on how to use it. + + +5. License(s) + +This BGPSEC code created by Parsons, Inc. + +(c) 2013-2016 Parsons, Inc. +All Rights Reserved + +Code within this patch is dual copyrighted under both the GPLv2+ and +the BSD license. It can be used under either license below: + + +GPLv2+ + +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or (at +your option) any later version. + +This program is distributed in the hope that it will be useful, but +WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 +USA + + +BSD + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +* Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + +* Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + +* Neither the name of Parsons, Inc nor the names of its contributors may + be used to endorse or promote products derived from this software + without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS ``AS +IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR +CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, +EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; +OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, +WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR +OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF +ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/configure.in b/configure.in index c81709e6..8b9afe39 100644 --- a/configure.in +++ b/configure.in @@ -10,6 +10,7 @@ AC_ARG_ENABLE(debug, [ --enable-debug enable internal debugging routin AC_ARG_ENABLE(memcheck, [ --enable-memcheck check memory allocations when debugging (default: enabled)],,enable_memcheck=yes) AC_ARG_ENABLE(client, [ --enable-client enable building of BIRD client (default: enabled)],,enable_client=yes) AC_ARG_ENABLE(ipv6, [ --enable-ipv6 enable building of IPv6 version (default: disabled)],,enable_ipv6=no) +AC_ARG_ENABLE(bgpsec,[ --enable-bgpsec enable building of bgp with security (default: disabled)],,enable_bgpsec=no) AC_ARG_ENABLE(pthreads, [ --enable-pthreads enable POSIX threads support (default: detect)],,enable_pthreads=try) AC_ARG_WITH(suffix, [ --with-suffix=STRING use specified suffix for BIRD files (default: 6 for IPv6 version)],[given_suffix="yes"]) AC_ARG_WITH(sysconfig, [ --with-sysconfig=FILE use specified BIRD system configuration file]) @@ -21,7 +22,6 @@ AC_ARG_VAR([FLEX], [location of the Flex program]) AC_ARG_VAR([BISON], [location of the Bison program]) AC_ARG_VAR([M4], [location of the M4 program]) - if test "$srcdir" = . ; then # Building in current directory => create obj directory holding all objects objdir=obj @@ -262,6 +262,21 @@ if test "$enable_debug" = yes ; then fi fi +AC_MSG_CHECKING([BGPsec enabled]) +if test "$enable_bgpsec" = yes ; then + AC_MSG_RESULT(yes) + protocols="$protocols bgp/bgpsec" + AC_CHECK_LIB(dl, dlopen) + AC_CHECK_LIB(crypto, PEM_read_X509) + AC_CHECK_LIB(crypto, EC_KEY_set_asn1_flag) + if test $ac_cv_lib_crypto_EC_KEY_set_asn1_flag != yes ; then + AC_MSG_ERROR([openssl: libcrypt does not support elliptical curves. EC support is required for BGPsec]) + fi + AC_DEFINE(CONFIG_BGPSEC) +else + AC_MSG_RESULT(no) +fi + CLIENT= CLIENT_LIBS= if test "$enable_client" = yes ; then @@ -304,6 +319,7 @@ BIRD was configured with the following options: Debugging: $enable_debug POSIX threads: $enable_pthreads Routing protocols: $protocols + BGPsec enabled: $enable_bgpsec Client: $enable_client EOF rm -f $objdir/.*-stamp diff --git a/lib/unaligned.h b/lib/unaligned.h index af655204..1b7fae55 100644 --- a/lib/unaligned.h +++ b/lib/unaligned.h @@ -19,6 +19,9 @@ #include "lib/string.h" +/* XXX need ifdef to intsead use bsd's #include */ +#include + static inline u16 get_u16(void *p) { @@ -35,6 +38,14 @@ get_u32(void *p) return ntohl(x); } +static inline u64 +get_u64(void *p) +{ + u64 x; + memcpy(&x, p, 8); + return be64toh(x); +} + static inline void put_u16(void *p, u16 x) { @@ -49,4 +60,11 @@ put_u32(void *p, u32 x) memcpy(p, &x, 4); } +static inline void +put_u64(void *p, u64 x) +{ + x = htobe64(x); + memcpy(p, &x, 8); +} + #endif diff --git a/nest/protocol.h b/nest/protocol.h index 8660cc2c..be776261 100644 --- a/nest/protocol.h +++ b/nest/protocol.h @@ -76,7 +76,7 @@ void protos_dump_all(void); extern struct protocol proto_device, proto_radv, proto_rip, proto_static, - proto_ospf, proto_pipe, proto_bgp, proto_bfd; + proto_ospf, proto_pipe, proto_bgp, proto_bgpsec, proto_bfd; /* * Routing Protocol Instance diff --git a/nest/route.h b/nest/route.h index 5ee04a30..b3e25926 100644 --- a/nest/route.h +++ b/nest/route.h @@ -407,7 +407,8 @@ typedef struct eattr { #define EAP_RIP 2 /* RIP */ #define EAP_OSPF 3 /* OSPF */ #define EAP_KRT 4 /* Kernel route attributes */ -#define EAP_MAX 5 +#define EAP_BGPSEC 5 /* BGPSEC attributes */ +#define EAP_MAX 6 #define EA_CODE(proto,id) (((proto) << 8) | (id)) #define EA_PROTO(ea) ((ea) >> 8) diff --git a/proto/bgp/attrs.c b/proto/bgp/attrs.c index 72b45d47..bbef9854 100644 --- a/proto/bgp/attrs.c +++ b/proto/bgp/attrs.c @@ -4,6 +4,14 @@ * (c) 2000 Martin Mares * * Can be freely distributed and used under the terms of the GNU GPL. + * + * + * Code added from Parsons, Inc. (BGPSEC additions) + * (c) 2013-2016 + * + * Can be used under either license: + * - Freely distributed and used under the terms of the GNU GPLv2. + * - Freely distributed and used under a BSD license, See README.bgpsec. */ #undef LOCAL_DEBUG @@ -19,9 +27,14 @@ #include "lib/resource.h" #include "lib/string.h" #include "lib/unaligned.h" +#include "stdio.h" #include "bgp.h" +#ifdef CONFIG_BGPSEC +#include "bgpsec/validate.h" +#endif + /* * UPDATE message error handling * @@ -60,7 +73,7 @@ static byte bgp_mandatory_attrs[] = { BA_ORIGIN, BA_AS_PATH -#ifndef IPV6 +#if !defined(IPV6) && !defined(CONFIG_BGPSEC) ,BA_NEXT_HOP #endif }; @@ -191,7 +204,7 @@ validate_as4_path(struct bgp_proto *p, struct adata *path) static int bgp_check_next_hop(struct bgp_proto *p UNUSED, byte *a, int len) { -#ifdef IPV6 +#if defined(IPV6) || defined(CONFIG_BGPSEC) return IGNORE; #else ip_addr addr; @@ -209,7 +222,7 @@ static void bgp_format_next_hop(eattr *a, byte *buf, int buflen UNUSED) { ip_addr *ipp = (ip_addr *) a->u.ptr->data; -#ifdef IPV6 +#if defined(IPV6) || defined(CONFIG_BGSPEC) /* in IPv6, we might have two addresses in NEXT HOP */ if ((a->u.ptr->length == NEXT_HOP_LENGTH) && ipa_nonzero(ipp[1])) { @@ -243,13 +256,13 @@ bgp_format_aggregator(eattr *a, byte *buf, int buflen UNUSED) } static int -bgp_check_community(struct bgp_proto *p UNUSED, byte *a UNUSED, int len) +bgp_check_community(struct bgp_proto *p, byte *buf, int len) { return ((len % 4) == 0) ? 0 : WITHDRAW; } static int -bgp_check_cluster_list(struct bgp_proto *p UNUSED, byte *a UNUSED, int len) +bgp_check_cluster_list(struct bgp_proto *p, byte *buf, int len) { return ((len % 4) == 0) ? 0 : 5; } @@ -261,20 +274,432 @@ bgp_format_cluster_list(eattr *a, byte *buf, int buflen) int_set_format(a->u.ptr, 0, -1, buf, buflen); } + +/* BGPsec Decode Functions */ + +#ifdef CONFIG_BGPSEC + +/* Creates an as_path from the bgpsec attribute secure_path + information and adds it to the rta struct. */ +/* The created as_path is used for local route determination and is + removed before sending out bgpsec updates */ +int bgpsec_create_aspath(rta *route, byte *secpath_p, u16 secp_len, struct linpool *pool) +{ + ea_list *ea; + struct adata *ad; + byte *secp = secpath_p; + secp_len -= 2; + + /* xxx how to handle memory allocation error? */ + ea = lp_alloc(pool, sizeof(ea_list) + sizeof(eattr)); + ea->next = route->eattrs; + route->eattrs = ea; + + ea->flags = 0; + ea->count = 1; + ea->attrs[0].id = EA_CODE(EAP_BGP, BA_AS_PATH); + ea->attrs[0].flags = BAF_TRANSITIVE; + ea->attrs[0].type = EAF_TYPE_AS_PATH; + + byte aspath_len = secp_len / 6; + int pattr_len = 2 + (4*aspath_len); + + ad = lp_alloc(pool, sizeof(struct adata) + pattr_len); + ea->attrs[0].u.ptr = ad; + ad->length = pattr_len; + + byte *asp = ad->data; + *asp++ = AS_PATH_SEQUENCE; + *asp++ = aspath_len; + + secp += 2; /* skip flags and pcount */ + while ( (asp < (ad->data + pattr_len)) && (secp < (secpath_p + secp_len)) ) + { + memcpy(asp, secp, 4); + asp += 4; + secp += 6; + } + + return 0; +} /* int bgpsec_create_aspath() */ + + +/* Marks the route as "Valid" by adding a valid attribute */ +int bgpsec_add_valid_attr(rta *route, struct linpool *pool) +{ + ea_list *ea; + + /* xxx how to handle memory allocation error? */ + ea = lp_alloc(pool, sizeof(ea_list) + sizeof(eattr)); + ea->next = route->eattrs; + route->eattrs = ea; + + ea->flags = 0; + ea->count = 1; + ea->attrs[0].id = EA_CODE(EAP_BGP, BA_INTERNAL_BGPSEC_VALID); + ea->attrs[0].flags = BAF_OPTIONAL; + ea->attrs[0].type = EAF_TYPE_INT; + /* value 1 is arbitrary, existence of the attribute indicates valid */ + ea->attrs[0].u.data = 1; + + return 0; + +} /* int bgpsec_add_valid_attr() */ + + +/* XXX subroutine used for debbugging */ +char * +hashbuff_to_string(u8 *hb, int len) +{ + static char ret[(2*BGPSEC_MAX_SIG_LENGTH)+21], *rp; + rp = ret; + int i; + bzero(ret, (2*BGPSEC_MAX_SIG_LENGTH)+21); + + for(i=0; ilocal_as, bgp->remote_as); + + byte *bgpSec_p = buf; + + /* hash length, origination < non-orig + (e.g. ~22+ octets < 10+last signature length) */ + static u8 hashBuff[BGPSEC_SIG_HASH_LENGTH]; + u8 *hash_p = hashBuff; + + /* clean out any previous data */ + bzero(hashBuff, BGPSEC_SIG_HASH_LENGTH); + + /* variables used by DO_NLRI macro below, defined in bgp.h: + p, start, x, len, len0, af, sub and goto 'done:' */ + struct bgp_proto *p = bgp; + byte *start = 0, *x = 0; + int len = 0, len0 = 0; + unsigned af = 0, sub = 0; + /* variables used by DECODE_PREFIX macro below, defined in bgp.h: + p, err, path_id, prefix, pxlen and goto 'done:' */ + int err = 0; + u32 path_id = 0; + ip_addr prefix = 0; + int pxlen = 0; + + /* Is it long enough to have a minimal valid bgpseg_path_attr */ + /* 43 = 9 (NLRI/SAFI/AFI/AlgoID) + 8 (sec path min) + 26 (sig block min) */ + if ( bgpSec_len < 43 ) { + log(L_WARN "decode_bgpsec: %d < %d: bad bgpsec attribute length: %d, ignoring", + bgp->local_as, bgp->remote_as, bgpSec_len); + return IGNORE; + } + + /* get secure path pointer */ + u16 secPath_len = get_u16(bgpSec_p); + byte *secPath_p = bgpSec_p + 2; + byte *secPathSeg_p = secPath_p; + + /* XXX, only handling a single signature block, should handle 1 or 2 */ + /* get signature block pointer */ + byte *sigBlock_p = bgpSec_p + secPath_len; + u16 sigBlock_len = get_u16(sigBlock_p); + int algoID = *(bgpSec_p + 2); + byte *sigSegment_p = sigBlock_p + 3; /* skip length value and algo ID byte */ + + /* check algorithm signature ID, we only support one algo. ID + currently */ + if ( BGPSEC_ALGO_ID != algoID ) + { + log(L_ERR "decode_bgpsec: %d < %d: Uknown Algorithm ID: %d, ignoring", + bgp->local_as, bgp->remote_as, algoID); + /* XXX return err unknown sig algo? only if there is no + * other known sig algo (e.g.. two sig blocks); */ + return IGNORE; + } + + /* Check secure path size, each segment is 6 octets long */ + if ( ( ((secPath_len - 2) % 6) != 0 ) || ((secPath_len + 25) > bgpSec_len) ) + { + log(L_WARN "decode_bgpsec: %d < %d: bad secure path length, ignoring", + bgp->local_as, bgp->remote_as); + /* xxx */ + /* return errr bad length */ + return IGNORE; + } + + /* if not expecting peer pcount=0, check to make sure first pcount!=0 */ + if ( ( bgp->cf->bgpsec_no_pcount0 ) && + ( 0 == *secPathSeg_p ) ) + { + log(L_WARN "decode_bgpsec: %d < %d : pcount = 0 not allowed from this peer, invalid", + bgp->local_as, bgp->remote_as); + /* xxx */ + /* return spcefic error? */ + return IGNORE; + } + + /* add target AS to signature hash */ + put_u32(hash_p, bgp->local_as); + hash_p += 4; + + /* get last (first in signature block) signature to check againts later */ + byte *lastSKI_p = sigSegment_p; + sigSegment_p += BGPSEC_SKI_LENGTH; + u16 lastSig_len = get_u16(sigSegment_p); + sigSegment_p += 2; + byte *lastSig_p = sigSegment_p; + sigSegment_p += lastSig_len; + + /* sanity check */ + if ( sigSegment_p > (bgpSec_p + bgpSec_len) ) + { + log(L_WARN "decode_bgpsec: %d < %d: bad first signature length: %d, ignoring", + bgp->local_as, bgp->remote_as, lastSig_len); + /* xxx */ + /* return errr bad length */ + return IGNORE; + } + + /* while loop through signature / secure path blocks to load signature hash */ + while ( ( sigSegment_p < (bgpSec_p + bgpSec_len) ) && + ( secPathSeg_p < (bgpSec_p + secPath_len) ) ) + { + /* put next signature segment in hash */ + int sigSegment_len = BGPSEC_SKI_LENGTH + 2 + get_u16(sigSegment_p + BGPSEC_SKI_LENGTH); + + /* check hashBuff space for adding signature segment (variable) + * and secure path segment (6 bytes) */ + if ( ( (sigSegment_p + sigSegment_len) > (bgpSec_p + bgpSec_len) ) || + ( (hash_p + sigSegment_len + 6) > (hashBuff + BGPSEC_SIG_HASH_LENGTH) ) + ) + { + log(L_WARN "decode_bgpsec: %d < %d: bad signature segment length: %d, or not enough space in hash buffer, ignoring", + bgp->local_as, bgp->remote_as, sigSegment_len); + /* xxx */ + /* return errr bad length */ + return IGNORE; + } + memcpy(hash_p, sigSegment_p, sigSegment_len); + sigSegment_p += sigSegment_len; + hash_p += sigSegment_len; + + memcpy(hash_p, secPathSeg_p, 6); + secPathSeg_p += 6; + hash_p += 6; + } + + byte *beginLastHash_p = hash_p - 4; + + /* should have one secure path segment left */ + if ( (secPathSeg_p + 6) != (bgpSec_p + secPath_len) ) { + log(L_WARN "decode_bgpsec: %d < %d: bad number of secure path and/or signature segments, ignoring", + bgp->local_as, bgp->remote_as); + } + memcpy(hash_p, secPathSeg_p, 6); + secPathSeg_p += 6; + hash_p += 6; + + + /* Get the NLRI, AFI, and SAFI information from the MP_REACH attribute */ + + /* Decode the MP_REACH attribute */ + /* macro DO_NLRI, defined in bgp.h, uses: + p, start, x, len, len0, af, sub and goto 'done:' + */ + /* macro DECODE_PREFIX, defined in bgp.h, uses: + p, err, path_id, prefix, pxlen and goto 'done:' + */ + + DO_NLRI(mp_reach) + { + /* check NEXT_HOP length */ + if (len < 1 || (*x != 4 && *x != 16 && *x != 32) || + len < *x + 2) + { + log(L_WARN "decode_bgpsec:o: %d < %d : bad mp_reach next hop length: %d, ignoring", + bgp->local_as, bgp->remote_as, *x); + return IGNORE; + } + /* skip next_hop length, next_hop addr, and a reserved byte */ + len -= *x + 2; + x += *x + 2; + + /* Get Prefix, + macro DECODE_PREFIX sets: prefix, pxlen. Defined in bgp.h*/ + DECODE_PREFIX(x, len); + /* only one prefix is allowed in a BGPSEC message */ + if ( len > 0 ) + { + /* XXX handle specific errors for logging */ + log(L_WARN "decode_bgpsec: %d < %d : bad NLRI length, ignoring", + bgp->local_as, bgp->remote_as); + return IGNORE; + } + + log(L_DEBUG "decode_bgpsec: %d < %d : using NLRI %I/%d\n", + bgp->local_as, bgp->remote_as, prefix, pxlen); + } + else { + /* unknown Address Family */ + return IGNORE; + } + + /* load algorithm suite identifier */ + *hash_p = algoID; + hash_p++; + + /* AFI and SAFI */ + bzero(hash_p, 1); /* zero high order AFI */ + hash_p++; + memcpy(hash_p, &af, 1); /* copy low order AFI */ + hash_p++; + memcpy(hash_p, &sub, 1); /* SAFI */ + hash_p++; + + /* NLRI */ + memcpy(hash_p, &pxlen, 1); /* prefix length */ + hash_p++; + int prefix_bytes = (pxlen + 7) / 8; + memcpy(hash_p, &prefix, prefix_bytes); /* prefix */ + hash_p += prefix_bytes; + + /* Check Signatures */ + + byte *endHash_p = hash_p; + hash_p = hashBuff; + u32 signersAS = 0; + int valid = 1; + /* cycle through signature hashBuffer and check signatures */ + while ( ( hash_p < beginLastHash_p ) && valid ) { + /* signers AS in hash_p at offset = 4 (target AS) + SKI length + 2 + * (sig length value) + next signature length + 1 (flags) + 1 + * (pcount) + */ + int nextSig_len = get_u16(hash_p + 4 + BGPSEC_SKI_LENGTH); + int asOffset = BGPSEC_SKI_LENGTH + nextSig_len + 8; + signersAS = get_u32(hash_p + asOffset); + + if ( BGPSEC_SIGNATURE_MATCH != + bgpsec_verify_signature_with_bin_ski + (bgp->cf, + hash_p, (endHash_p - hash_p), + lastSKI_p, BGPSEC_SKI_LENGTH, + signersAS, algoID, + lastSig_p, lastSig_len) + ) { + if ( bgp->cf->bgpsec_no_invalid_routes ) { + log(L_WARN "decode_bgpsec: %d < %d : bad signature at AS: %d, invalid routes not allowed, ignoring", + bgp->local_as, bgp->remote_as, signersAS); + return IGNORE; + } + else { + log(L_WARN "decode_bgpsec: %d < %d : bad signature at AS: %d, not BGPsec valid", + bgp->local_as, bgp->remote_as, signersAS); + valid = 0; + } + } + else { + log(L_DEBUG "decode_bgpsec: %d < %d : good signature AS: %d", + bgp->local_as, bgp->remote_as, signersAS); + } + + /* adjust pointers, note: hash_p gets move to the next target + * AS/next secure path AS */ + lastSKI_p = hash_p + 4; + lastSig_p = hash_p + 6 + BGPSEC_SKI_LENGTH; + lastSig_len = nextSig_len; + hash_p += asOffset; + } + + /* check last, origination, signature */ + if ( valid ) { + signersAS = get_u32(hash_p + 6); + if ( BGPSEC_SIGNATURE_MATCH != + bgpsec_verify_signature_with_bin_ski + (bgp->cf, + hash_p, (endHash_p - hash_p), + lastSKI_p, BGPSEC_SKI_LENGTH, + signersAS, algoID, + lastSig_p, lastSig_len) + ) { + if ( bgp->cf->bgpsec_no_invalid_routes ) { + log(L_WARN "decode_bgpsec: %d < %d : bad last signature AS: %d, invalid routes not allowed, ignoring", + bgp->local_as, bgp->remote_as, signersAS); + return IGNORE; + } + else { + log(L_WARN "decode_bgpsec: %d < %d : bad last signature AS: %d, not BGPsec valid", + bgp->local_as, bgp->remote_as, signersAS); + valid = 0; + } + } + else { + log(L_DEBUG "decode_bgpsec: %d < %d : good last sig. AS: %d, marked BGPsec valid", + bgp->local_as, bgp->remote_as, signersAS); + /* mark route as valid */ + if ( 0 < bgpsec_add_valid_attr(route_attr, pool) ) { + /* xxx currently should never happen, get rid of check? */ + log(L_WARN "decode_bgpsec: %d < %d : unable to add valid attribute, failing", + bgp->local_as, bgp->remote_as); + return IGNORE; + } + } + } + + /* Create a local as_path to use for route selection */ + if ( 0 < bgpsec_create_aspath(route_attr, secPath_p, secPath_len, pool) ) { + /* xxx currently should never happen, get rid of check? */ + log(L_WARN "decode_bgpsec: %d < %d : unable to create local as4path, ignoring", + bgp->local_as, bgp->remote_as); + return IGNORE; + } + + return 0; + + /* goto 'done:' Used by the DO_NLRI and DECODE_PREFIX macros used + above and defined in bgp.h + */ + done: + log(L_WARN "bgpsec_decode: %d < %d : failed decoding NLRI: %d, ignoring", + bgp->local_as, bgp->remote_as, err); + return IGNORE; + +} /* static int decode_bgpsec_attr */ + +#endif +/* end BGPsec Decode Functions */ + + static int bgp_check_reach_nlri(struct bgp_proto *p UNUSED, byte *a UNUSED, int len UNUSED) { -#ifdef IPV6 p->mp_reach_start = a; p->mp_reach_len = len; -#endif + return IGNORE; } static int bgp_check_unreach_nlri(struct bgp_proto *p UNUSED, byte *a UNUSED, int len UNUSED) { -#ifdef IPV6 +#if defined(IPV6) || defined(CONFIG_BGPSEC) p->mp_unreach_start = a; p->mp_unreach_len = len; #endif @@ -289,41 +714,60 @@ bgp_check_ext_community(struct bgp_proto *p UNUSED, byte *a UNUSED, int len) static struct attr_desc bgp_attr_table[] = { - { NULL, -1, 0, 0, 0, /* Undefined */ + { NULL, -1, 0, 0, 0, /* 0 Undefined */ NULL, NULL }, - { "origin", 1, BAF_TRANSITIVE, EAF_TYPE_INT, 1, /* BA_ORIGIN */ + { "origin", 1, BAF_TRANSITIVE, EAF_TYPE_INT, 1, /* 1 BA_ORIGIN */ bgp_check_origin, bgp_format_origin }, - { "as_path", -1, BAF_TRANSITIVE, EAF_TYPE_AS_PATH, 1, /* BA_AS_PATH */ + { "as_path", -1, BAF_TRANSITIVE, EAF_TYPE_AS_PATH, 1, /* 2 BA_AS_PATH */ NULL, NULL }, /* is checked by validate_as_path() as a special case */ - { "next_hop", 4, BAF_TRANSITIVE, EAF_TYPE_IP_ADDRESS, 1, /* BA_NEXT_HOP */ + { "next_hop", 4, BAF_TRANSITIVE, EAF_TYPE_IP_ADDRESS, 1, /* 3 BA_NEXT_HOP */ bgp_check_next_hop, bgp_format_next_hop }, - { "med", 4, BAF_OPTIONAL, EAF_TYPE_INT, 1, /* BA_MULTI_EXIT_DISC */ + { "med", 4, BAF_OPTIONAL, EAF_TYPE_INT, 1, /* 4 BA_MULTI_EXIT_DISC */ NULL, NULL }, - { "local_pref", 4, BAF_TRANSITIVE, EAF_TYPE_INT, 0, /* BA_LOCAL_PREF */ + { "local_pref", 4, BAF_TRANSITIVE, EAF_TYPE_INT, 0, /* 5 BA_LOCAL_PREF */ NULL, NULL }, - { "atomic_aggr", 0, BAF_TRANSITIVE, EAF_TYPE_OPAQUE, 1, /* BA_ATOMIC_AGGR */ + { "atomic_aggr", 0, BAF_TRANSITIVE, EAF_TYPE_OPAQUE, 1, /* 6 BA_ATOMIC_AGGR */ NULL, NULL }, - { "aggregator", -1, BAF_OPTIONAL | BAF_TRANSITIVE, EAF_TYPE_OPAQUE, 1, /* BA_AGGREGATOR */ + { "aggregator", -1, BAF_OPTIONAL | BAF_TRANSITIVE, EAF_TYPE_OPAQUE, 1, /* 7 BA_AGGREGATOR */ bgp_check_aggregator, bgp_format_aggregator }, - { "community", -1, BAF_OPTIONAL | BAF_TRANSITIVE, EAF_TYPE_INT_SET, 1, /* BA_COMMUNITY */ + { "community", -1, BAF_OPTIONAL | BAF_TRANSITIVE, EAF_TYPE_INT_SET, 1, /* 8 BA_COMMUNITY */ bgp_check_community, NULL }, - { "originator_id", 4, BAF_OPTIONAL, EAF_TYPE_ROUTER_ID, 0, /* BA_ORIGINATOR_ID */ + { "originator_id", 4, BAF_OPTIONAL, EAF_TYPE_ROUTER_ID, 0, /* 9 BA_ORIGINATOR_ID */ NULL, NULL }, - { "cluster_list", -1, BAF_OPTIONAL, EAF_TYPE_INT_SET, 0, /* BA_CLUSTER_LIST */ + { "cluster_list", -1, BAF_OPTIONAL, EAF_TYPE_INT_SET, 0, /* 10 BA_CLUSTER_LIST */ bgp_check_cluster_list, bgp_format_cluster_list }, - { .name = NULL }, /* BA_DPA */ - { .name = NULL }, /* BA_ADVERTISER */ - { .name = NULL }, /* BA_RCID_PATH */ - { "mp_reach_nlri", -1, BAF_OPTIONAL, EAF_TYPE_OPAQUE, 1, /* BA_MP_REACH_NLRI */ + { .name = NULL }, /* 11 BA_DPA */ + { .name = NULL }, /* 12 BA_ADVERTISER */ + { .name = NULL }, /* 13 BA_RCID_PATH */ + { "mp_reach_nlri", -1, BAF_OPTIONAL, EAF_TYPE_OPAQUE, 1, /* 14 BA_MP_REACH_NLRI */ bgp_check_reach_nlri, NULL }, - { "mp_unreach_nlri", -1, BAF_OPTIONAL, EAF_TYPE_OPAQUE, 1, /* BA_MP_UNREACH_NLRI */ + { "mp_unreach_nlri", -1, BAF_OPTIONAL, EAF_TYPE_OPAQUE, 1, /* 15 BA_MP_UNREACH_NLRI */ bgp_check_unreach_nlri, NULL }, - { "ext_community", -1, BAF_OPTIONAL | BAF_TRANSITIVE, EAF_TYPE_EC_SET, 1, /* BA_EXT_COMMUNITY */ + { "ext_community", -1, BAF_OPTIONAL | BAF_TRANSITIVE, EAF_TYPE_EC_SET, 1, /* 16 BA_EXT_COMMUNITY */ bgp_check_ext_community, NULL }, - { "as4_path", -1, BAF_OPTIONAL | BAF_TRANSITIVE, EAF_TYPE_OPAQUE, 1, /* BA_AS4_PATH */ + { "as4_path", -1, BAF_OPTIONAL | BAF_TRANSITIVE, EAF_TYPE_OPAQUE, 1, /* 17 BA_AS4_PATH */ NULL, NULL }, - { "as4_aggregator", -1, BAF_OPTIONAL | BAF_TRANSITIVE, EAF_TYPE_OPAQUE, 1, /* BA_AS4_PATH */ - NULL, NULL } + { "as4_aggregator", -1, BAF_OPTIONAL | BAF_TRANSITIVE, EAF_TYPE_OPAQUE, 1, /* 18 BA_AS4_AGGREGATOR */ + NULL, NULL }, + /* not supported attributes */ + { .name = NULL }, /* 19 BA_SSA */ + { .name = NULL }, /* 20 BA_CONNECTOR_ATTR */ + { .name = NULL }, /* 21 BA_AS_PATHLIMIT */ + { .name = NULL }, /* 22 BA_PMSI_TUNNEL */ + { .name = NULL }, /* 23 BA_TUNNEL_ENCAP */ + { .name = NULL }, /* 24 BA_TUNNEL_ENGINEERING */ + { .name = NULL }, /* 25 BA_IPV6_EXT_COMMUNITY */ + { .name = NULL }, /* 26 BA_AIGP */ + { .name = NULL }, /* 27 BA_PE_DIST_LABELS */ + { .name = NULL }, /* 28 BA_ENTROPY_LABELS */ + { .name = NULL }, /* 29 BA_LS_ATTRIBUTE */ + /* supported */ +#ifdef CONFIG_BGPSEC + /* Treated as a special case and checked by decode_bgpsec_attr, + bgpsec_authenticate, and encode_bgpsec_attr */ + { "bgpsec_signature", -1, BAF_OPTIONAL, EAF_TYPE_OPAQUE, 1, /* 30 BA_BGPSEC_SIGNATURE */ + NULL, NULL }, +#endif }; /* BA_AS4_PATH is type EAF_TYPE_OPAQUE and not type EAF_TYPE_AS_PATH. @@ -459,8 +903,294 @@ bgp_get_attr_len(eattr *a) return len; } + #define ADVANCE(w, r, l) do { r -= l; w += l; } while (0) +#ifdef CONFIG_BGPSEC + +/* BGPSEC Encode Function */ +/* For the originating AS, add a bgpsec signature attribute to the update */ +/* Otherwise, add an additional signature to the bgpsec signature attribute */ +/* Returns length of attribute added, 0 if no attribute added, and < 0 + on error */ +unsigned int +encode_bgpsec_attr(struct bgp_conn *conn, + ea_list *attr_list, + byte *w, + int remains, + byte *nlri) +{ + log(L_TRACE "encode_bgpsec_attr: %d > %d", + conn->bgp->local_as, conn->bgp->remote_as); + + eattr *asPathAttr = ea_find(attr_list, EA_CODE(EAP_BGP, BA_AS_PATH)); + eattr *bgpSecAttr = ea_find(attr_list, EA_CODE(EAP_BGP, BA_BGPSEC_SIGNATURE)); + + if ( NULL == asPathAttr ) { + log(L_ERR "encode_bgpsec_attr: Error: %d > %d : AS_Path dose not exists", + conn->bgp->local_as, conn->bgp->remote_as); + return -1; + } + + u8 *pathPtr = (u8 *)&(asPathAttr->u.ptr->data); + int numOfAS = (asPathAttr->u.ptr->length - 2) / 4; + + log(L_DEBUG "encode_bgpsec_attr: %d > %d : #AS: %d", + conn->bgp->local_as, conn->bgp->remote_as, numOfAS); + + /* if this route does not have a BGPsec attribute and this is not + * the origination, do not add a BGPsec attribute to this update */ + if ( (NULL == bgpSecAttr ) && ( numOfAS > 1 ) ) { + log(L_DEBUG "encode_bgpsec_attr: %d > %d : No BGPsec attribute for this non origination route (#AS %d), BGPsec attribute not added", + conn->bgp->local_as, conn->bgp->remote_as, numOfAS); + return 0; + } + + /* must be as_sequence, as_set not allowed for bgpsec */ + if ( pathPtr[0] != AS_PATH_SEQUENCE ) { + log(L_ERR "encode_bgpsec_attr: Error: %d > %d : AS_Path that is not AS_PATH_SEQUENCE not allowed", + conn->bgp->local_as, conn->bgp->remote_as); + return -1; + } + + byte *start = w; + + static u8 sigBuff[BGPSEC_MAX_SIG_LENGTH]; + static u8 hashBuff[BGPSEC_SIG_HASH_LENGTH]; + u8 *hash_p = hashBuff; + + /* clean out any previous data in buffers */ + bzero(sigBuff, BGPSEC_MAX_SIG_LENGTH); + bzero(hashBuff, BGPSEC_SIG_HASH_LENGTH); + + int signature_len = 0; + char oMark = 'O'; + + + /* load signature hash buffer */ + + /* add target AS */ + put_u32(hash_p, conn->bgp->remote_as); + hash_p += 4; + + /* secure path data */ + byte *secPath_p = NULL; + u16 secPath_len = 2; /* default to local secure path header size */ + byte *secPathSeg_p = NULL; + + /* signature block data */ + byte *sigBlock_p = NULL; + u16 sigBlock_len = 3; /* default to sig block header size */ + byte *sigSegment_p = NULL; + int sigSegment_len = 0; + + /* get hash data from bgp attribute, if we are not the originator, + place first signature segment in hash */ + if ( NULL != bgpSecAttr ) { + oMark = 'N'; + /* get secure path pointer */ + secPath_p = (byte *)&(bgpSecAttr->u.ptr->data); + secPath_len = get_u16(secPath_p); + secPathSeg_p = secPath_p + 2; /* skip past secure path length value */ + + /* XXX, only handling a single signature block, should handle 1 or 2 */ + /* get signature block pointer */ + sigBlock_p = secPath_p + secPath_len; + sigBlock_len = get_u16(sigBlock_p); + sigSegment_p = sigBlock_p + 3; /* skip length value and algo ID byte */ + + /* put first signature segment in hash */ + /* length is signature length, plus 2B length val + SKI length */ + sigSegment_len = get_u16(sigSegment_p + BGPSEC_SKI_LENGTH) + BGPSEC_SKI_LENGTH + 2; + /* buffer size check */ + if ( (hash_p + sigSegment_len) > (hashBuff + BGPSEC_SIG_HASH_LENGTH) ) { + log(L_ERR + "encode_bgpsec_attr: Error: signature segment larger than hash buffer size"); + return -1; + } + memcpy(hash_p, sigSegment_p, sigSegment_len); + hash_p += sigSegment_len; + sigSegment_p += sigSegment_len; + } + + /* Add our own secure path segment */ + /* pcount = 1, XXX configurable */ + *hash_p = 1 ; + hash_p += 1; + /* flags */ + *hash_p = 0x00; + hash_p += 1; + + /* our AS */ + put_u32(hash_p, conn->bgp->local_as); + hash_p += 4; + + /* If we are not origination, put following sequence of signature + and secure path segments in hash */ + if ( NULL != bgpSecAttr ) { + while ( (sigSegment_p < (sigBlock_p + sigBlock_len)) && + (secPathSeg_p < (secPath_p + secPath_len)) ) { + + /* put next signature segment in hash */ + sigSegment_len = get_u16(sigSegment_p + BGPSEC_SKI_LENGTH) + BGPSEC_SKI_LENGTH + 2; + + /* buffer size check, include secure path (6 bytes) */ + if ( (hash_p + sigSegment_len + 6) > (hashBuff + BGPSEC_SIG_HASH_LENGTH) ) { + log(L_ERR + "encode_bgpsec_attr: Error: signature/secure path segment larger than hash buffer size"); + return -1; + } + memcpy(hash_p, sigSegment_p, sigSegment_len); + hash_p += sigSegment_len; + sigSegment_p += sigSegment_len; + + /* put next secure path segment in hash */ + memcpy(hash_p, secPathSeg_p, 6); + secPathSeg_p += 6; + hash_p += 6; + } + + /* add last secure path segment */ + /* buffer size check, include secure path (6 bytes) */ + if ( ( (hash_p + 6) > (hashBuff + BGPSEC_SIG_HASH_LENGTH) ) && + ( secPathSeg_p < (secPath_p + secPath_len) ) ) { + log(L_ERR + "encode_bgpsec_attr: Error: last secure path segment larger than hash buffer size or missing"); + return -1; + } + + /* put last secure path segment in hash */ + memcpy(hash_p, secPathSeg_p, 6); + secPathSeg_p += 6; + hash_p += 6; + } + + /* get NLRI information */ + u8 px_len = *nlri++; + int pxBytes = (px_len+7) / 8; + ip_addr prefix; + bzero(&prefix, sizeof(ip_addr)); + memcpy(&prefix, nlri, pxBytes); + ipa_ntoh(prefix); + + log(L_DEBUG "encode_bgpsec_attr: %d > %d, using NLRI %I/%d\n", + conn->bgp->local_as, conn->bgp->remote_as, prefix, px_len); + + /* buffer size check */ + if ( (hash_p + 5 + pxBytes) > (hashBuff + BGPSEC_SIG_HASH_LENGTH) ) { + log(L_ERR + "encode_bgpsec_attr: Error: not enough hash buffer space for AlgoID/AFI/SAFI/NLRI"); + return -1; + } + + /* algorithm suite identifier */ + *hash_p = BGPSEC_ALGO_ID; + hash_p++; + + /* AFI */ +#ifdef IPV6 + put_u16(hash_p, BGP_AF_IPV6); +#else + put_u16(hash_p, BGP_AF_IPV4); +#endif + hash_p += 2; + /* SAFI */ + *hash_p = 1; /* SAFI unicast */ + hash_p++; + + /* NLRI */ + *hash_p = px_len; + hash_p++; + memcpy(hash_p, &prefix, pxBytes); + hash_p += pxBytes; + + /* sign */ + signature_len = bgpsec_sign_data_with_ascii_ski(conn->bgp->cf, + hashBuff, (hash_p - hashBuff), + conn->bgp->cf->bgpsec_ski, + strlen(conn->bgp->cf->bgpsec_ski), + conn->bgp->local_as, BGPSEC_ALGO_ID, + sigBuff, BGPSEC_MAX_SIG_LENGTH); + + if ( 1 >= signature_len ) { + log(L_ERR "encode_bgpsec_attr:%c: %d > %d, Signing Failed", + oMark, conn->bgp->local_as, conn->bgp->remote_as); + return -1; + } + else { + log(L_DEBUG "encode_bgpsec_attr:%c: Signed %d > %d, signature length = %d", + oMark, conn->bgp->local_as, conn->bgp->remote_as, signature_len); + } + + /* BGPsec Attribute length */ + /* attribute value length + (old secure path length + new secure + path segment length) + (old signature block length + new + signature segment length) */ + int bgpsecAttr_len = (secPath_len + 6) + (sigBlock_len + 2 + signature_len + BGPSEC_SKI_LENGTH); + + /* just single sig block XXX */ + /* is there enough room for adding a new signature */ + if ( remains < bgpsecAttr_len ) { + log(L_ERR "encode_bgpsec_attr: %d > %d, not enough room for bgpsec attribute: %d", + conn->bgp->local_as, conn->bgp->remote_as, bgpsecAttr_len ); + return -1; + } + + /* Create outgoing BGPsec attribute */ + /* attribute header */ + /* 4 (attr header) + secure path length + signature block length */ + int rv = bgp_encode_attr_hdr(w, BAF_OPTIONAL, BA_BGPSEC_SIGNATURE, + bgpsecAttr_len); + ADVANCE(w, remains, rv); + + /* secure path header (len) */ + put_u16(w, (secPath_len + 6)); + ADVANCE(w, remains, 2); + /* Add our own secure path segment */ + /* pcount = 1, XXX configurable */ + *w = 0x01; + ADVANCE(w, remains, 1); + /* flags */ + *w = 0x00; + ADVANCE(w, remains, 1); + /* our AS */ + put_u32(w, conn->bgp->local_as); + ADVANCE(w, remains, 4); + + /* old secure path, if it exists (not origination) */ + if ( NULL != secPath_p ) { + memcpy(w, (secPath_p + 2), (secPath_len - 2)); + ADVANCE(w, remains, (secPath_len - 2)); + } + + /* signature block header (length and algorithm ID) */ + put_u16(w, (sigBlock_len + 2 + signature_len + BGPSEC_SKI_LENGTH)); + ADVANCE(w, remains, 2); + *w = BGPSEC_ALGO_ID; + ADVANCE(w, remains, 1); + + /* new signature segment */ + memcpy(w, conn->bgp->cf->bgpsec_bski, BGPSEC_SKI_LENGTH); + ADVANCE(w, remains, BGPSEC_SKI_LENGTH); + put_u16(w, signature_len); + ADVANCE(w, remains, 2); + memcpy(w, sigBuff, signature_len); + ADVANCE(w, remains, signature_len); + + /* old signature segments, if they exists (not origination) */ + if ( NULL != sigBlock_p ) { + memcpy(w, (sigBlock_p + 3), (sigBlock_len - 3)); + ADVANCE(w, remains, (sigBlock_len - 3)); + } + + return (w - start); + +} /* int encode_bgpsec_attr */ + +#endif +/* End BGPsec Sign Function */ + + /** * bgp_encode_attrs - encode BGP attributes * @p: BGP instance @@ -486,12 +1216,33 @@ bgp_encode_attrs(struct bgp_proto *p, byte *w, ea_list *attrs, int remains) ASSERT(EA_PROTO(a->id) == EAP_BGP); code = EA_ID(a->id); -#ifdef IPV6 +#if defined(IPV6) || defined(CONFIG_BGSPEC) /* When talking multiprotocol BGP, the NEXT_HOP attributes are used only temporarily. */ if (code == BA_NEXT_HOP) continue; #endif +#ifdef CONFIG_BGPSEC + /* Do not send internally used extended attribute. + * Do not handle the BPGsec attribute here. */ + if ( code == BA_INTERNAL_BGPSEC_VALID || + code == BA_BGPSEC_SIGNATURE ) { + continue; + } + + /* Do not send AS_PATH with the BGPsec attribute. */ + /* If this is an AS_PATH and the connection is configured for + * BPGsec, do not add the AS_PATH attribute if a BGPsec + * attribute exists or this is the originatian for the prefix, + * ie. AS_Path <= 1 */ + if ( ( code == BA_AS_PATH ) && ( p->cf->enable_bgpsec ) && + ( ( ea_find(attrs, EA_CODE(EAP_BGP, BA_BGPSEC_SIGNATURE)) ) || + ( 1 >= (a->u.ptr->length - 2) / 4 ) ) + ) { + continue; + } +#endif + /* When AS4-aware BGP speaker is talking to non-AS4-aware BGP speaker, * we have to convert our 4B AS_PATH to 2B AS_PATH and send our AS_PATH * as optional AS4_PATH attribute. @@ -616,6 +1367,7 @@ bgp_encode_attrs(struct bgp_proto *p, byte *w, ea_list *attrs, int remains) } ADVANCE(w, remains, len); } + return w - start; err_no_buffer: @@ -834,7 +1586,12 @@ bgp_get_bucket(struct bgp_proto *p, net *n, ea_list *attrs, int originate) /* Hash */ hash = ea_hash(new); for(b=p->bucket_hash[hash & (p->hash_size - 1)]; b; b=b->hash_next) - if (b->hash == hash && ea_same(b->eattrs, new)) + if ( (b->hash == hash && ea_same(b->eattrs, new)) +#ifdef CONFIG_BGPSEC + /* multiple prefixes not allowed in BGPSEC NLRI*/ + && (!p->conn->peer_bgpsec_support) +#endif + ) { DBG("Found bucket.\n"); return b; @@ -1217,6 +1974,19 @@ bgp_rte_better(rte *new, rte *old) if (n < o) return 0; +#ifdef CONFIG_BGPSEC + if ( new_bgp->cf->bgpsec_prefer || old_bgp->cf->bgpsec_prefer ) { + /* Somewhat arbitrary (after local pref before as_path, ordering + * placement for bgpsec validity check */ + x = ea_find(new->attrs->eattrs, EA_CODE(EAP_BGP, BA_INTERNAL_BGPSEC_VALID)); + y = ea_find(old->attrs->eattrs, EA_CODE(EAP_BGP, BA_INTERNAL_BGPSEC_VALID)); + n = x ? 1 : 0; + o = y ? 1 : 0; + if (n > o) return 1; + if (n < o) return 0; + } +#endif + /* RFC 4271 9.1.2.2. a) Use AS path lengths */ if (new_bgp->cf->compare_path_lengths || old_bgp->cf->compare_path_lengths) { @@ -1593,17 +2363,27 @@ bgp_remove_as4_attrs(struct bgp_proto *p, rta *a) * by a &rta. */ struct rta * -bgp_decode_attrs(struct bgp_conn *conn, byte *attr, unsigned int len, struct linpool *pool, int mandatory) +bgp_decode_attrs(struct bgp_conn *conn, byte *attr, unsigned int len, + struct linpool *pool, byte *nlri, int nlri_len) { struct bgp_proto *bgp = conn->bgp; rta *a = lp_alloc(pool, sizeof(struct rta)); unsigned int flags, code, l, i, type; int errcode; - byte *z, *attr_start; + byte *z=0, *attr_start=0; byte seen[256/8]; ea_list *ea; struct adata *ad; int withdraw = 0; + int mandatory = nlri_len; +#ifdef CONFIG_BGPSEC + unsigned int bgpsec_len = 0; + byte *bgpsec_start = 0; +#endif +/* mp_reach attr is required for ipv6 or bgpsec, see mandatory check below */ +#if defined(IPV6) || defined(CONFIG_BGPSEC) + mandatory = 0; +#endif bzero(a, sizeof(rta)); a->source = RTS_BGP; @@ -1650,9 +2430,9 @@ bgp_decode_attrs(struct bgp_conn *conn, byte *attr, unsigned int len, struct lin { struct attr_desc *desc = &bgp_attr_table[code]; if (desc->expected_length >= 0 && desc->expected_length != (int) l) - { errcode = 5; goto err; } + { errcode = BGP_UPD_ERROR_ATTR_LENGTH; goto err; } if ((desc->expected_flags ^ flags) & (BAF_OPTIONAL | BAF_TRANSITIVE)) - { errcode = 4; goto err; } + { errcode = BGP_UPD_ERROR_ATTR_FLAG; goto err; } if (!desc->allow_in_ebgp && !bgp->is_internal) continue; if (desc->validate) @@ -1673,20 +2453,51 @@ bgp_decode_attrs(struct bgp_conn *conn, byte *attr, unsigned int len, struct lin { /* Special case as it might also trim the attribute */ if (validate_as_path(bgp, z, &l) < 0) - { errcode = 11; goto err; } + { errcode = BGP_UPD_ERROR_MALFORMED_ASPATH; goto err; } } +#ifdef CONFIG_BGPSEC + else if (code == BA_BGPSEC_SIGNATURE) + { + log(L_DEBUG "UPDATE: message has BA_BGPSEC_SIGNATURE"); + /* Special case, attribute must be parsed and + cryptographically checked. */ + /* AS_PATH should not be in the same update with a + BGPSEC_SIGNATURE attribute, check that a AS_PATH + attribute has not already been seen and mark it as + seen. */ + if (seen[BA_AS_PATH/8] & (1 << (BA_AS_PATH%8))) + goto malformed; + /* Note: It is mandatory for an update to have either a + AS_PATH or a BGPSEC_SIGNATURE attribute. AS_PATH is + set to 'seen' here to cover both the mandatory and + exclusivity requirements. */ + seen[BA_AS_PATH/8] |= (1 << (BA_AS_PATH%8)); + /* Only handle BGPsec if connection is configured for + * BGPsec and the peer supports BGPsec, otherwise this + * fails because there is no AS_PATH */ + if (!bgp->cf->enable_bgpsec || !bgp->conn->peer_bgpsec_support) { + log(L_WARN "UPDATE: malformed: recieved BGPsec attribute, but connection not configured for BGPsec or peer does not support"); + goto malformed; + } + /* bgpsec requires mp_reach attribute, so bgpsec + * decoding must occur after the attribute parsing + * loop, save attr info here */ + bgpsec_start = z; + bgpsec_len = l; + } +#endif type = desc->type; } else /* Unknown attribute */ { if (!(flags & BAF_OPTIONAL)) - { errcode = 2; goto err; } + { errcode = BGP_UPD_ERROR_UNRCGNZD_WK_ATTR; goto err; } type = EAF_TYPE_OPAQUE; } // Only OPTIONAL and TRANSITIVE attributes may have non-zero PARTIAL flag // if (!((flags & BAF_OPTIONAL) && (flags & BAF_TRANSITIVE)) && (flags & BAF_PARTIAL)) - // { errcode = 4; goto err; } + // { errcode = BGP_UPD_ERROR_ATTR_FLAG; goto err; } seen[code/8] |= (1 << (code%8)); ea = lp_alloc(pool, sizeof(ea_list) + sizeof(eattr)); @@ -1732,7 +2543,7 @@ bgp_decode_attrs(struct bgp_conn *conn, byte *attr, unsigned int len, struct lin if (withdraw) goto withdraw; -#ifdef IPV6 +#if defined(IPV6) || defined(CONFIG_BGPSEC) /* If we received MP_REACH_NLRI we should check mandatory attributes */ if (bgp->mp_reach_len != 0) mandatory = 1; @@ -1753,6 +2564,21 @@ bgp_decode_attrs(struct bgp_conn *conn, byte *attr, unsigned int len, struct lin } } +#ifdef CONFIG_BGPSEC + if ( bgp->cf->bgpsec_require && + (0 == bgpsec_len || 0 == bgpsec_start) ) { + log(L_WARN "UPDATE: malformed: BGPsec attribute required but not in Update"); + goto malformed; + } + + if ( (0 != bgpsec_len) && (0 != bgpsec_start) ) { + if ( decode_bgpsec_attr(bgp, bgpsec_start, bgpsec_len, a, pool) < 0 ) { + errcode = BGP_UPD_ERROR_MALFORMED_ATTR; + goto err; + } + } +#endif + /* When receiving attributes from non-AS4-aware BGP speaker, * we have to reconstruct 4B AS_PATH and AGGREGATOR attributes */ @@ -1780,7 +2606,7 @@ withdraw: return NULL; malformed: - bgp_error(conn, 3, 1, NULL, 0); + bgp_error(conn, 3, BGP_UPD_ERROR_MALFORMED_ATTR, NULL, 0); return NULL; err: diff --git a/proto/bgp/bgp.c b/proto/bgp/bgp.c index e48b643b..763c9952 100644 --- a/proto/bgp/bgp.c +++ b/proto/bgp/bgp.c @@ -4,6 +4,14 @@ * (c) 2000 Martin Mares * * Can be freely distributed and used under the terms of the GNU GPL. + * + * + * Code added from Parsons, Inc. (BGPSEC additions) + * (c) 2013-2013 + * + * Can be used under either license: + * - Freely distributed and used under the terms of the GNU GPLv2. + * - Freely distributed and used under a BSD license, See README.bgpsec. */ /** @@ -78,6 +86,10 @@ #include "bgp.h" +#ifdef CONFIG_BGPSEC +/* sscanf parsing of SKI configuration value */ +#include +#endif struct linpool *bgp_linpool; /* Global temporary pool */ static sock *bgp_listen_sk; /* Global listening socket */ @@ -1266,7 +1278,6 @@ bgp_check_config(struct bgp_config *c) if (c->c.class == SYM_TEMPLATE) return; - /* EBGP direct by default, IBGP multihop by default */ if (c->multihop < 0) c->multihop = internal ? 64 : 0; @@ -1283,7 +1294,6 @@ bgp_check_config(struct bgp_config *c) if (c->c.in_limit && (c->c.in_limit->action == PLA_RESTART) && c->disable_after_error) c->c.in_limit->action = PLA_DISABLE; - if (!c->local_as) cf_error("Local AS number must be set"); @@ -1329,6 +1339,61 @@ bgp_check_config(struct bgp_config *c) if (c->secondary && !c->c.table->sorted) cf_error("BGP with secondary option requires sorted table"); + +#ifdef CONFIG_BGPSEC + /* create a binary SKI from config */ + if ( c->enable_bgpsec ) { + if ( strnlen(c->bgpsec_ski, (2 * BGPSEC_SKI_LENGTH)) + != (BGPSEC_SKI_LENGTH * 2) ) { + cf_error("BGPSEC: bad length of the configured SKI value"); + } + + if ( BGPSEC_SKI_LENGTH != + sscanf(c->bgpsec_ski, "%2hhx%2hhx%2hhx%2hhx%2hhx%2hhx%2hhx%2hhx%2hhx%2hhx%2hhx%2hhx%2hhx%2hhx%2hhx%2hhx%2hhx%2hhx%2hhx%2hhx", + (unsigned char *)c->bgpsec_bski, + (unsigned char *)(c->bgpsec_bski+1), + (unsigned char *)(c->bgpsec_bski+2), + (unsigned char *)(c->bgpsec_bski+3), + (unsigned char *)(c->bgpsec_bski+4), + (unsigned char *)(c->bgpsec_bski+5), + (unsigned char *)(c->bgpsec_bski+6), + (unsigned char *)(c->bgpsec_bski+7), + (unsigned char *)(c->bgpsec_bski+8), + (unsigned char *)(c->bgpsec_bski+9), + (unsigned char *)(c->bgpsec_bski+10), + (unsigned char *)(c->bgpsec_bski+11), + (unsigned char *)(c->bgpsec_bski+12), + (unsigned char *)(c->bgpsec_bski+13), + (unsigned char *)(c->bgpsec_bski+14), + (unsigned char *)(c->bgpsec_bski+15), + (unsigned char *)(c->bgpsec_bski+16), + (unsigned char *)(c->bgpsec_bski+17), + (unsigned char *)(c->bgpsec_bski+18), + (unsigned char *)(c->bgpsec_bski+19)) ) { + cf_error("BGPSEC: unable to parse the configured SKI value"); + } + + + log(L_WARN "BPGPSEC: bgpsec_key_repo_path is %s", c->bgpsec_key_repo_path); + if (c->bgpsec_key_repo_path) { + int krp = strnlen(c->bgpsec_key_repo_path, 10); + if ( 0 < krp && krp < 2 ) { + log(L_WARN "BPGPSEC: unable to parse bpgsec_key_repo_path: %d", krp); + cf_error("BGPSEC:: unable to parse bpgsec_key_repo_path"); + } + } + log(L_WARN "BPGPSEC: bgpsec_key_repo_path is %s", c->bgpsec_priv_key_path); + if (c->bgpsec_priv_key_path) { + int pkp = strnlen(c->bgpsec_priv_key_path, 10); + if ( 0 < pkp && pkp < 2 ) { + log(L_WARN "BPGPSEC: unable to parse bpgsec_key_repo_path: %d", pkp); + cf_error("BGPSEC:: unable to parse bpgsec_key_repo_path"); + } + } + + } +#endif + } static int diff --git a/proto/bgp/bgp.h b/proto/bgp/bgp.h index f4f21226..e4adf5cb 100644 --- a/proto/bgp/bgp.h +++ b/proto/bgp/bgp.h @@ -4,6 +4,14 @@ * (c) 2000 Martin Mares * * Can be freely distributed and used under the terms of the GNU GPL. + * + * + * Code added from Parsons, Inc. (BGPSEC additions) + * (c) 2013-2013 + * + * Can be used under either license: + * - Freely distributed and used under the terms of the GNU GPLv2. + * - Freely distributed and used under a BSD license, See README.bgpsec. */ #ifndef _BIRD_BGP_H_ @@ -17,6 +25,24 @@ struct linpool; struct eattr; +#ifdef CONFIG_BGPSEC +/* BGPSec constants */ +#define BGPSEC_VERSION 0 +/* currently capability is arbitrary number from private use */ +#define BGPSEC_CAPABILITY 212 +#define BGPSEC_SKI_LENGTH 20 +#define BGPSEC_ALGO_ID 1 /* XXX this needs to be changed */ +#define BGPSEC_MAX_SIG_LENGTH 80 + /* sig hash length is somewhat arbitrary, + = 20 + MaxASPathLength*(28 + max_sig_length). + As of 2016, max unique AS Path length found is 14. + This value will allowy for for a hash buffer that can handle an AS + path length ~47 long + */ +#define BGPSEC_SIG_HASH_LENGTH 5120 +#define BGPSEC_MAX_INFO_ATTR_LENGTH 0 /* XXX this needs to be checked */ +#endif + struct bgp_config { struct proto_config c; u32 local_as, remote_as; @@ -40,6 +66,20 @@ struct bgp_config { int capabilities; /* Enable capability handshake [RFC3392] */ int enable_refresh; /* Enable local support for route refresh [RFC2918] */ int enable_as4; /* Enable local support for 4B AS numbers [RFC4893] */ + + /* BGPSec */ + /* cannot be ifdef'd out due to config.Y compatibility */ + int enable_bgpsec; /* Whether neighbor should be a BGPSec peer */ + int bgpsec_prefer; /* Whether validly signed BGPsec routes are prefered during route selection */ + int bgpsec_require; /* Whether neighbor should be a BGPSec peer */ + char *bgpsec_ski; /* local subject key id */ + u8 bgpsec_bski[BGPSEC_SKI_LENGTH]; /* binary local SKI */ + char *bgpsec_key_repo_path; /* Path to the public key repository */ + char *bgpsec_priv_key_path; /* Path to the private key location */ + int bgpsec_save_binary_keys; /* Save a copy of the binary key */ + int bgpsec_no_pcount0; /* allow peer to have pcount 0, xxx current default allows */ + int bgpsec_no_invalid_routes; /* should invalid routes be dropped */ + u32 rr_cluster_id; /* Route reflector cluster ID, if different from local ID */ int rr_client; /* Whether neighbor is RR client of me */ int rs_client; /* Whether neighbor is RS client of me */ @@ -100,6 +140,12 @@ struct bgp_conn { byte *notify_data; u32 advertised_as; /* Temporary value for AS number received */ int start_state; /* protocol start_state snapshot when connection established */ + +#ifdef CONFIG_BGPSEC + /* BGPsec */ + u8 peer_bgpsec_support; /* Peer supports BGPSec */ +#endif + u8 peer_refresh_support; /* Peer supports route refresh [RFC2918] */ u8 peer_as4_support; /* Peer supports 4B AS numbers [RFC4893] */ u8 peer_add_path; /* Peer supports ADD-PATH [draft] */ @@ -117,6 +163,15 @@ struct bgp_proto { struct bgp_config *cf; /* Shortcut to BGP configuration */ u32 local_as, remote_as; int start_state; /* Substates that partitions BS_START */ + +#ifdef CONFIG_BGPSEC + /* BGPsec */ + u8 bgpsec_send; /* Sender can send BGPSec messages */ + u8 bgpsec_receive; /* Sender can receive BGPSec messages */ + u8 bgpsec_ipv4; /* Sender uses BGPSec over iPv4 */ + u8 bgpsec_ipv6; /* Sender uses BGPSec over iPv6 */ +#endif + u8 is_internal; /* Internal BGP connection (local_as == remote_as) */ u8 as4_session; /* Session uses 4B AS numbers in AS_PATH (both sides support it) */ u8 add_path_rx; /* Session expects receive of ADD-PATH extended NLRI */ @@ -152,7 +207,7 @@ struct bgp_proto { u8 last_error_class; /* Error class of last error */ u32 last_error_code; /* Error code of last error. BGP protocol errors are encoded as (bgp_err_code << 16 | bgp_err_subcode) */ -#ifdef IPV6 +#if defined(IPV6) || defined CONFIG_BGPSEC byte *mp_reach_start, *mp_unreach_start; /* Multiprotocol BGP attribute notes */ unsigned mp_reach_len, mp_unreach_len; ip_addr local_link; /* Link-level version of source_addr */ @@ -235,7 +290,7 @@ static inline void set_next_hop(byte *b, ip_addr addr) { ((ip_addr *) b)[0] = ad void bgp_attach_attr(struct ea_list **to, struct linpool *pool, unsigned attr, uintptr_t val); byte *bgp_attach_attr_wa(struct ea_list **to, struct linpool *pool, unsigned attr, unsigned len); -struct rta *bgp_decode_attrs(struct bgp_conn *conn, byte *a, unsigned int len, struct linpool *pool, int mandatory); +struct rta *bgp_decode_attrs(struct bgp_conn *conn, byte *attr, unsigned int len, struct linpool *pool, byte * nlri, int nlri_len); int bgp_get_attr(struct eattr *e, byte *buf, int buflen); int bgp_rte_better(struct rte *, struct rte *); int bgp_rte_recalculate(rtable *table, net *net, rte *new, rte *old, rte *old_best); @@ -278,6 +333,8 @@ void bgp_log_error(struct bgp_proto *p, u8 class, char *msg, unsigned code, unsi #define BAF_PARTIAL 0x20 #define BAF_EXT_LEN 0x10 +/* Note: these must match location in the bgp_attr_table */ + #define BA_ORIGIN 0x01 /* [RFC1771] */ /* WM */ #define BA_AS_PATH 0x02 /* WM */ #define BA_NEXT_HOP 0x03 /* WM */ @@ -287,16 +344,34 @@ void bgp_log_error(struct bgp_proto *p, u8 class, char *msg, unsigned code, unsi #define BA_AGGREGATOR 0x07 /* OT */ #define BA_COMMUNITY 0x08 /* [RFC1997] */ /* OT */ #define BA_ORIGINATOR_ID 0x09 /* [RFC1966] */ /* ON */ -#define BA_CLUSTER_LIST 0x0a /* ON */ +#define BA_CLUSTER_LIST 0x0a /* [RFC4456] */ /* We don't support these: */ -#define BA_DPA 0x0b /* ??? */ -#define BA_ADVERTISER 0x0c /* [RFC1863] */ -#define BA_RCID_PATH 0x0d -#define BA_MP_REACH_NLRI 0x0e /* [RFC2283] */ -#define BA_MP_UNREACH_NLRI 0x0f -#define BA_EXT_COMMUNITY 0x10 /* [RFC4360] */ -#define BA_AS4_PATH 0x11 /* [RFC4893] */ -#define BA_AS4_AGGREGATOR 0x12 +#define BA_DPA 0x0b /* DPA deprecated */ +#define BA_ADVERTISER 0x0c /* [RFC1863] */ +#define BA_RCID_PATH 0x0d /* [RFC1863] */ +/* supported? */ +#define BA_MP_REACH_NLRI 0x0e /* [RFC4760] */ +#define BA_MP_UNREACH_NLRI 0x0f /* [RFC4760] */ +#define BA_EXT_COMMUNITY 0x10 /* [RFC4360] */ +#define BA_AS4_PATH 0x11 /* [RFC6793] */ +#define BA_AS4_AGGREGATOR 0x12 /* [RFC6793] */ +/* not supported */ +#define BA_SSA 0x13 /* SAFI Specific Attribute (SSA) (deprecated) */ +#define BA_CONNECTOR_ATTR 0x14 /* (deprecated) [RFC6037] */ +#define BA_AS_PATHLIMIT 0x15 /* (deprecated) [draft-ietf-idr-as-pathlimit] */ +#define BA_PMSI_TUNNEL 0x16 /* [RFC6514] */ +#define BA_TUNNEL_ENCAP 0x17 /* Tunnel Encapsulation [RFC5512] */ +#define BA_TUNNEL_ENGINEERING 0x18 /* Traffic Engineering [RFC5543] */ +#define BA_IPV6_EXT_COMMUNITY 0x19 /* IPv6 Address Specific Extended Community [RFC5701] */ +#define BA_AIGP 0x1a /* AIGP (TEMPORARY, expired 2013-04-25) [draft-ietf-idr-aigp][Rex_Fernando][Pradosh_Mohapatra][Eric_Rosen][James_Uttaro] */ +#define BA_PE_DIST_LABELS 0x1b /* PE Distinguisher Labels [RFC6514] */ +#define BA_ENTROPY_LABELS 0x1c /* BGP Entropy Label Capability Attribute [RFC6790] */ +#define BA_LS_ATTRIBUTE 0x1d /* BGP-LS Attribute (TEMPORARY, expired 2014-03-11) [draft-ietf-idr-ls-distribution] */ + +/* Supported */ +#define BA_BGPSEC_SIGNATURE 0x1E /* XXX 30 is best guess, draft-ietf-sidr-bgpsec-protocol */ +/* internal use only */ +#define BA_INTERNAL_BGPSEC_VALID 0xdd /* BGP connection states */ @@ -376,6 +451,18 @@ void bgp_log_error(struct bgp_proto *p, u8 class, char *msg, unsigned code, unsi #define BEA_ROUTE_LIMIT_EXCEEDED 1 +/* BGP Update Error codes */ +#define BGP_UPD_ERROR_MALFORMED_ATTR 1 +#define BGP_UPD_ERROR_UNRCGNZD_WK_ATTR 2 +#define BGP_UPD_ERROR_MISSING_WK_ATTR 3 +#define BGP_UPD_ERROR_ATTR_FLAG 4 +#define BGP_UPD_ERROR_ATTR_LENGTH 5 +#define BGP_UPD_ERROR_INVALID_ORGIN 6 +#define BGP_UPD_ERROR_INVALID_HOP 8 +#define BGP_UPD_ERROR_OPT_ATTR 9 +#define BGP_UPD_ERROR_INVALID_NETWORK 10 +#define BGP_UPD_ERROR_MALFORMED_ASPATH 11 + /* Well-known communities */ #define BGP_COMM_NO_EXPORT 0xffffff01 /* Don't export outside local AS / confed. */ @@ -399,4 +486,43 @@ void bgp_log_error(struct bgp_proto *p, u8 class, char *msg, unsigned code, unsi #define BGP_AF BGP_AF_IPV4 #endif +#define DO_NLRI(name) \ + start = x = p->name##_start; \ + len = len0 = p->name##_len; \ + if (len) \ + { \ + if (len < 3) { err=9; goto done; } \ + af = get_u16(x); \ + sub = x[2]; \ + x += 3; \ + len -= 3; \ + DBG("\tNLRI AF=%d sub=%d len=%d\n", af, sub, len);\ + } \ + else \ + af = 0; \ +if ((af == BGP_AF_IPV6) || (af == BGP_AF_IPV4)) + + +#define DECODE_PREFIX(pp, ll) do { \ + if (p->add_path_rx) \ + { \ + if (ll < 5) { err=1; goto done; } \ + path_id = get_u32(pp); \ + pp += 4; \ + ll -= 4; \ + } \ + int b = *pp++; \ + int q; \ + ll--; \ + if (b > BITS_PER_IP_ADDRESS) { err=10; goto done; } \ + q = (b+7) / 8; \ + if (ll < q) { err=1; goto done; } \ + memcpy(&prefix, pp, q); \ + pp += q; \ + ll -= q; \ + ipa_ntoh(prefix); \ + prefix = ipa_and(prefix, ipa_mkmask(b)); \ + pxlen = b; \ +} while (0) + #endif diff --git a/proto/bgp/bgpsec/Makefile b/proto/bgp/bgpsec/Makefile new file mode 100644 index 00000000..fdd923ca --- /dev/null +++ b/proto/bgp/bgpsec/Makefile @@ -0,0 +1,11 @@ +source=validate.c +root-rel=../../../ +dir-name=proto/bgp/bgpsec + +include ../../../Rules + +validate_tmp.o: ../../../../proto/bgp/bgpsec/validate.c + gcc -c $(CFLAGS) -DLOG_TO_STDERR -o $@ $< + +tests: tests.o validate_tmp.o + gcc -o $@ $^ bgpsec.o $(LDFLAGS) $(LIBS) diff --git a/proto/bgp/bgpsec/bgpsec-create-hash-dir b/proto/bgp/bgpsec/bgpsec-create-hash-dir new file mode 100755 index 00000000..805215d1 --- /dev/null +++ b/proto/bgp/bgpsec/bgpsec-create-hash-dir @@ -0,0 +1,190 @@ +#!/usr/bin/perl + +use File::Find; +use strict; +my %opts = + ( + 'openssl' => "/usr/local/openssl-ecdsa/bin", + 'f' => "\.cer\$", + 'i' => "DER", + ); + +LocalGetOptions(\%opts, + ['GUI:otherargs', '[input-directory] [output-directory]'], + ["GUI:separator", "Processing Configuration"], + ["f|file-regexp=s", "Regexp of filenames to select from"], + ["s|suffix=s", "Use STRING as the new extension suffix"], + ["i|input-format=s", "Input format type of files found"], + + ["GUI:separator", "Basic Configuration"], + ["openssl|openssl-path=s", "Path to OpenSSL install directory with EC support"], + + ["GUI:separator", "Output Options"], + ["v|verbose", "Be verbose about what is being done"], + ); + +my $inputDirectory = $ARGV[0]; +my $outputDirectory = $ARGV[1]; + +my $opensslPath = $opts{'openssl'}; +my $fileRegexp = $opts{'f'}; +my $inform = $opts{'i'}; + +# ensure we have input and output +if (!defined($inputDirectory) || !defined($outputDirectory)) { + print STDERR "Both an input and output directory are required\n"; + exit 1; +} + +# add in a separate openssl path +if ($opensslPath) { + $ENV{'PATH'} = $opensslPath . ":" . $ENV{'PATH'}; +} + +if (! -d $outputDirectory) { + Verbose("creating $outputDirectory\n"); + mkdir($outputDirectory); +} + +# find and process every file of certain types +Verbose("Searching directory $inputDirectory\n"); +find({no_chdir => 1, wanted => \&convert_file}, $inputDirectory); + +sub convert_file { + if ($File::Find::name =~ /$fileRegexp/io) { + my $ski; + # process it... + Verbose("Found $File::Find::name\n"); + open(SKI, "openssl x509 -inform $inform -in $File::Find::name -text|"); + while() { + if (/X509v3 Subject Key Identifier/) { + $ski = ; + $ski =~ s/\s//g; + $ski =~s/://g; + last; + } + } + close(SKI); + + if (defined($ski)) { + my $suffix; + if ($opts{'s'}) { + $suffix = $opts{'s'}; + } else { + $suffix = $File::Find::name; + $suffix =~ s/.*\.//; + } + + Verbose(" linking to $ski.$suffix\n"); + symlink($File::Find::name, "$outputDirectory/$ski.$suffix"); + } + } +} + +###################################################################### +# support functions +# + +sub Verbose { + print STDERR @_ if ($opts{'v'}); +} + +###################################################################### +# Getopt bootstrapping +# +sub LocalGetOptions { + if (eval {require Getopt::GUI::Long;}) { + import Getopt::GUI::Long; + # optional configure call + Getopt::GUI::Long::Configure(qw(display_help no_gui no_ignore_case allow_zero)); + return GetOptions(@_); + } + require Getopt::Long; + import Getopt::Long; + # optional configure call + Getopt::Long::Configure(qw(auto_help no_ignore_case)); + GetOptions(LocalOptionsMap(@_)); +} + +sub LocalOptionsMap { + my ($st, $cb, @opts) = ((ref($_[0]) eq 'HASH') + ? (1, 1, $_[0]) : (0, 2)); + for (my $i = $st; $i <= $#_; $i += $cb) { + if ($_[$i]) { + next if (ref($_[$i]) eq 'ARRAY' && $_[$i][0] =~ /^GUI:/); + push @opts, ((ref($_[$i]) eq 'ARRAY') ? $_[$i][0] : $_[$i]); + push @opts, $_[$i+1] if ($cb == 2); + } + } + return @opts; +} + +1; + +=pod + +=head1 NAME + +bgpsec-create-hash-dir - creates a SKI-based hash directory + +=head1 SYNOPSIS + +bgpsec-create-hash-dir /path/to/rpki-authenticated-dir /path/to/hash-dir + +=head1 OPTIONS + +=head2 Processing Configuration + +=over 4 + +=item -f STRING + + + +=item --file-regexp=STRING + +Regexp of filenames to select from + +=item -s STRING + + + +=item --suffix=STRING + +Use STRING as the new extension suffix + +=item -i STRING + +=item --input-format=STRING + +Input format of files found. Defaults to DER. + +=back + +=head2 Basic Configuration + +=over 4 + +=item --openssl=STRING + + + +=item --openssl-path=STRING + +Path to OpenSSL install directory with EC support + +=back + +=head2 Output Options + +=over 4 + +=item -v + + + +=item --verbose + +Be verbose about what is being done + +=back diff --git a/proto/bgp/bgpsec/config.Y b/proto/bgp/bgpsec/config.Y new file mode 100644 index 00000000..d3a7f3b4 --- /dev/null +++ b/proto/bgp/bgpsec/config.Y @@ -0,0 +1,27 @@ +/* + * BIRD -- The Border Gateway Protocol + * + * + * Parsons, Inc. + * (c) 2013-2013 + * + * Code can be used under either license: + * - Freely distributed and used under the terms of the GNU GPLv2. + * - Freely distributed and used under a BSD license, See README.bgpsec. + */ + +CF_HDR + +/* #include "proto/bgp/bgpsec/bgpsec.h" */ + +CF_DEFINES + +CF_DECLS + +CF_KEYWORDS(BGPSEC_CONFIG) + +CF_GRAMMAR + +CF_CODE + +CF_END diff --git a/proto/bgp/bgpsec/keytool.py b/proto/bgp/bgpsec/keytool.py new file mode 100755 index 00000000..97e6bfa9 --- /dev/null +++ b/proto/bgp/bgpsec/keytool.py @@ -0,0 +1,225 @@ +#!/usr/bin/env python +# +# Copyright (C) 2014 Dragon Research Labs ("DRL") +# +# Permission to use, copy, modify, and/or distribute this software for any +# purpose with or without fee is hereby granted, provided that the above +# copyright notice and this permission notice appear in all copies. +# +# THE SOFTWARE IS PROVIDED "AS IS" AND DRL DISCLAIMS ALL WARRANTIES WITH +# REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY +# AND FITNESS. IN NO EVENT SHALL DRL BE LIABLE FOR ANY SPECIAL, DIRECT, +# INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM +# LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE +# OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR +# PERFORMANCE OF THIS SOFTWARE. + +""" +Key management tool for revised version of Sparta's BGPSEC implementation. +""" + +import os +import sys +import argparse +import subprocess + + +default_openssl_binary = os.getenv("BGPSEC_OPENSSL_BINARY", "openssl") +default_public_key_dir = os.getenv("BGPSEC_PUBLIC_KEY_DIR", "/usr/share/bird/bgpsec-keys") +default_private_key_dir = os.getenv("BGPSEC_PRIVATE_KEY_DIR", "/usr/share/bird/bgpsec-private-keys") + + +class OpenSSLPipeline(object): + """ + String together one or more OpenSSL commands in a pipeline, return + stdout of the final command. Callable object rather than function + so we can instantiate it as a closure over the program arguments. + """ + + allowed_keywords = set(["input"]) + + def __init__(self, args): + self.args = args + + def __call__(self, *argses, **kwargs): + assert all(kw in self.allowed_keywords for kw in kwargs) + procs = [] + for args in argses: + procs.append(subprocess.Popen((self.args.openssl_binary,) + args, + stdout = subprocess.PIPE, + stdin = procs[-1].stdout if procs else subprocess.PIPE)) + if "input" in kwargs: + procs[0].stdin.write(kwargs["input"]) + procs[0].stdin.close() + output = procs[-1].stdout.read() + for i, proc in enumerate(procs): + if proc.wait() != 0: + raise subprocess.CalledProcessError(proc.returncode, argses[i][0]) + return output +# class OpenSSLPipeline(object): + + +def public_filename(args, asn, skihex): + """ + Figure out what the filename for a key should be, and create the + containing directory if it doesn't already exist. + """ + + for n in xrange(args.max_ski_collisions): + fn = "%s/%s.%s.%s.key" % (args.public_key_dir, asn, skihex, n) + if args.skip_collision_check or not os.path.exists(fn): + break + else: + sys.exit("Too many SKI collisions for ASN %s SKI %s" % (asn, skihex)) + dn = os.path.dirname(fn) + if not os.path.isdir(dn): + if args.verbose: + print "Creating directory", dn + os.makedirs(dn) + return fn +# def public_filename(args, asn, skihex): + + +def generate(args): + """ + Generate an EC keypair, store in .key files named using the key's + SKI value to generate the filenames. + """ + + # We go through some silly gymnastics using the old OpenSSL ecparam + # command instead of using the newer OpenSSL genpkey command, + # because we have to force the key into the required namedCurve form + # instead of explicitCurve. OpenSSL itself doesn't much care, but + # since the SKI is defined as the SHA1 hash of the binary key value, + # using the wrong key encoding yields the wrong SKI value. + + openssl = OpenSSLPipeline(args) + pemkey = openssl(("ecparam", "-name", "prime256v1"), + ("ecparam", "-param_enc", "named_curve", "-genkey")) + pemkey = pemkey.splitlines(True) + pemkey = "".join(pemkey[pemkey.index("-----BEGIN EC PRIVATE KEY-----\n"):]) + skihex = openssl(("pkey", "-outform", "DER", "-pubout"), + ("dgst", "-sha1", "-hex"), + input = pemkey) + skihex = skihex.split()[-1].upper() + if args.printski: + print skihex + fn = public_filename(args, args.asns[0], skihex) + if args.verbose: + print "Writing", fn + openssl(("pkey", "-outform", "DER", "-out", fn, "-pubout"), input = pemkey) + for asn in args.asns[1:]: + ln = public_filename(args, asn, skihex) + if args.verbose: + print "Linking", ln + os.link(fn, ln) + os.umask(077) + fn = "%s/%s.%s.key" % (args.private_key_dir, args.asns[0], skihex) + if args.verbose: + print "Writing", fn + openssl(("pkey", "-outform", "DER", "-out", fn), input = pemkey) + for asn in args.asns[1:]: + ln = "%s/%s.%s.key" % (args.private_key_dir, asn, skihex) + if args.verbose: + print "Linking", ln + os.link(fn, ln) +# def generate(args): + + +def hashdir(args): + """ + Extract router keys from certificates in an RPKI certificate tree, + store as .key files using each key's SKI value to generate the + corresponding filename. + """ + + openssl = OpenSSLPipeline(args) + for root, dirs, files in os.walk(args.cert_dir): + for fn in files: + if fn.endswith(".cer"): + fn = os.path.join(root, fn) + text = openssl(("x509", "-inform", "DER", "-noout", "-text", "-in", fn)) + if "Public Key Algorithm: id-ecPublicKey" not in text or "ASN1 OID: prime256v1" not in text: + continue + if args.verbose: + print "Examining", fn + skihex = text[text.index("X509v3 Subject Key Identifier:"):].splitlines()[1].strip().replace(":", "").upper() + if args.paranoia: + checkski = openssl(("x509", "-inform", "DER", "-noout", "-pubkey", "-in", fn), + ("pkey", "-pubin", "-outform", "DER"), + ("dgst", "-sha1", "-hex")) + checkski = checkski.split()[-1].upper() + if skihex != checkski: + sys.stderr.write("SKI %s in certificate %s does not match calculated SKI %s\n" % (skihex, fn, checkski)) + asns = [] + b = text.index("Autonomous System Numbers:") + e = text.index("\n\n", b) + for line in text[b:e].splitlines()[1:]: + b, _, e = line.strip().partition("-") + if e == "": + asns.append(int(b)) + else: + asns.extend(xrange(int(b), int(e) + 1)) + outfn = public_filename(args, asns[0], skihex) + if args.verbose: + print "Writing", outfn + openssl(("x509", "-inform", "DER", "-noout", "-pubkey", "-in", fn), + ("pkey", "-pubin", "-outform", "DER", "-out", outfn)) + for asn in asns[1:]: + ln = public_filename(args, asn, skihex) + if args.verbose: + print "Linking", ln + os.link(outfn, ln) +# def hashdir(args): + + +def main(): + parser = argparse.ArgumentParser(description = __doc__) + parser.add_argument("--openssl-binary", + default = default_openssl_binary, + help = "Path to EC-capable OpenSSL binary") + parser.add_argument("--public-key-dir", + default = default_public_key_dir, + help = "directory to which we save parsed router keys") + parser.add_argument("--private-key-dir", + default = default_private_key_dir, + help = "directory to which we save generated private keys") + parser.add_argument("--verbose", + action = "store_true", + help = "whistle while you work") + parser.add_argument("--printski", + action = "store_true", + help = "print out the SKI value") + parser.add_argument("--paranoia", + action = "store_true", + help = "perform paranoid checks") + parser.add_argument("--max-ski-collisions", + type = int, + default = 3, + help = "maximum number of SKI collisions to allow when writing public keys") + parser.add_argument("--skip-collision-check", + action = "store_true", + help = "don't check for SKI collisions") + subparsers = parser.add_subparsers(title = "Commands", + metavar = "") + subparser = subparsers.add_parser("generate", + description = generate.__doc__, + help = "generate new keypair") + subparser.set_defaults(func = generate) + subparser.add_argument("--router-id", + type = int) + subparser.add_argument("asns", + nargs = "+", + type = int) + subparser = subparsers.add_parser("hashdir", + description = hashdir.__doc__, + help = "hash directory of certs") + subparser.set_defaults(func = hashdir) + subparser.add_argument("cert_dir") + args = parser.parse_args() + return args.func(args) +# def main(): + + +if __name__ == "__main__": + sys.exit(main()) diff --git a/proto/bgp/bgpsec/router-key.cnf b/proto/bgp/bgpsec/router-key.cnf new file mode 100644 index 00000000..cb4cff60 --- /dev/null +++ b/proto/bgp/bgpsec/router-key.cnf @@ -0,0 +1,350 @@ +# +# OpenSSL example configuration file. +# This is mostly being used for generation of certificate requests. +# + +# This definition stops the following lines choking if HOME isn't +# defined. +HOME = . +RANDFILE = $ENV::HOME/.rnd + +# To use this configuration file with the "-extfile" option of the +# "openssl x509" utility, name here the section containing the +# X.509v3 extensions to use: +extensions = bgpsec_router_ext + +oid_section = new_oids + +[openssl_init] + +# Extra OBJECT IDENTIFIER info: +oid_section = new_oids + +[ x509 ] +extensions = bgpsec_router_ext + +[ new_oids ] + +# XXX: replace with the real assignments at some point +# (from draft-ietf-sidr-bgpsec-pki-profiles-00.txt) +id_kp_bgpsec_router = 1.3.6.1.5.5.7.99.99 + +#extKeyUsage = 2.5.29.37 + +#################################################################### +#################################################################### +# +# CA definition +# +[ ca ] +default_ca = bgpsec_CA # The default ca section + +[ bgpsec_CA ] + +dir = . # Where everything is kept +certs = $dir/certs # Where the issued certs are kept +crl_dir = $dir/crl # Where the issued crl are kept +database = $dir/index.txt # database index file. +#unique_subject = no # Set to 'no' to allow creation of + # several ctificates with same + # subject. +new_certs_dir = $dir/newcerts # default place for new certs. + +certificate = $dir/cacert.pem # The CA certificate +serial = $dir/serial # The current serial number +crlnumber = $dir/crlnumber # the current crl number + # must be commented out to + # leave a V1 CRL +crl = $dir/crl.pem # The current CRL +private_key = $dir/private/cakey.pem# The private key +RANDFILE = $dir/private/.rand # private random number file + +x509_extensions = usr_cert # The extentions to add to the cert + +# Comment out the following two lines for the "traditional" +# (and highly broken) format. +name_opt = ca_default # Subject Name options +cert_opt = ca_default # Certificate field options + +# Extension copying option: use with caution. +# copy_extensions = copy + +# Extensions to add to a CRL. Note: Netscape communicator chokes on V2 CRLs +# so this is commented out by default to leave a V1 CRL. +# crlnumber must also be commented out to leave a V1 CRL. +# crl_extensions = crl_ext + +default_days = 365 # how long to certify for +default_crl_days= 30 # how long before next CRL +default_md = default # use public key default MD +preserve = no # keep passed DN ordering + +# A few difference way of specifying how similar the request should look +# For type CA, the listed attributes must be the same, and the optional +# and supplied fields are just that :-) +policy = policy_match + +# For the CA policy +[ policy_match ] +countryName = match +stateOrProvinceName = match +organizationName = match +organizationalUnitName = optional +commonName = supplied +emailAddress = optional + +# For the 'anything' policy +# At this point in time, you must list all acceptable 'object' +# types. +[ policy_anything ] +countryName = optional +stateOrProvinceName = optional +localityName = optional +organizationName = optional +organizationalUnitName = optional +commonName = supplied +emailAddress = optional + +#################################################################### +# +# CSR (req)uest generation +# +[ req ] +default_bits = 2048 +default_md = sha256 +default_keyfile = privkey.pem +distinguished_name = req_distinguished_name +attributes = req_attributes +encrypt_key = no +x509_extensions = bgpsec_router_ext # The extentions to add to the self signed cert + +# This sets a mask for permitted string types. There are several options. +# default: PrintableString, T61String, BMPString. +# pkix : PrintableString, BMPString (PKIX recommendation before 2004) +# utf8only: only UTF8Strings (PKIX recommendation after 2004). +# nombstr : PrintableString, T61String (no BMPStrings or UTF8Strings). +# MASK:XXXX a literal mask value. +# WARNING: ancient versions of Netscape crash on BMPStrings or UTF8Strings. +string_mask = utf8only + +req_extensions = bgpsec_router_csr # The extensions to add to a certificate request + +[ req_distinguished_name ] +commonName = special RPKI field... need to gen it. +commonName_max = 64 +commonName_default = finishMeBecauseThisIsWrong + +# SET-ex3 = SET extension number 3 + +[ req_attributes ] + +[ usr_cert ] + +# These extensions are added when 'ca' signs a request. + +# This goes against PKIX guidelines but some CAs do it and some software +# requires this to avoid interpreting an end user certificate as a CA. + +basicConstraints=CA:FALSE + +# Here are some examples of the usage of nsCertType. If it is omitted +# the certificate can be used for anything *except* object signing. + +# This is OK for an SSL server. +# nsCertType = server + +# For an object signing certificate this would be used. +# nsCertType = objsign + +# For normal client use this is typical +# nsCertType = client, email + +# and for everything including object signing: +# nsCertType = client, email, objsign + +# This is typical in keyUsage for a client certificate. +# keyUsage = nonRepudiation, digitalSignature, keyEncipherment + +# This will be displayed in Netscape's comment listbox. +nsComment = "OpenSSL Generated Certificate" + +# PKIX recommendations harmless if included in all certificates. +subjectKeyIdentifier=hash +authorityKeyIdentifier=keyid,issuer + +# This stuff is for subjectAltName and issuerAltname. +# Import the email address. +# subjectAltName=email:copy +# An alternative to produce certificates that aren't +# deprecated according to PKIX. +# subjectAltName=email:move + +# Copy subject details +# issuerAltName=issuer:copy + +#nsCaRevocationUrl = http://www.domain.dom/ca-crl.pem +#nsBaseUrl +#nsRevocationUrl +#nsRenewalUrl +#nsCaPolicyUrl +#nsSslServerName + +# This is required for TSA certificates. +# extendedKeyUsage = critical,timeStamping + +[ v3_req ] + +# Extensions to add to a certificate request + +basicConstraints = CA:FALSE +keyUsage = nonRepudiation, digitalSignature, keyEncipherment + +[bgpsec_router_ext] +# 3.1.3.1. Extended Key Usage +#extKeyUsage = id_kp_bgpsec_router +keyUsage = digitalSignature +basicConstraints=CA:FALSE +subjectKeyIdentifier=hash +#extendedKeyUsage = id_kp_bgpsec_router +extendedKeyUsage = 1.3.6.1.5.5.7.99.99 + +#extKeyUsage = id_kp_bgpsec_router + +# 3.1.3.4. AS Resources +# as specified in section 4.8.11 of [ID.sidr-res-cert-profile] +#asNumber = AS number for this router (digits only) +#asNumber_default = XXXX + +[bgpsec_router_csr] +# defined in 3.2. BGPSEC Router Certificate Request Profile +# 3.1.3.1. Extended Key Usage +extendedKeyUsage = id_kp_bgpsec_router +subjectKeyIdentifier=hash + +[ v3_ca ] + + +# Extensions for a typical CA + + +# PKIX recommendation. + +subjectKeyIdentifier=hash + +authorityKeyIdentifier=keyid:always,issuer + +# This is what PKIX recommends but some broken software chokes on critical +# extensions. +#basicConstraints = critical,CA:true +# So we do this instead. +basicConstraints = CA:true + +# Key usage: this is typical for a CA certificate. However since it will +# prevent it being used as an test self-signed certificate it is best +# left out by default. +# keyUsage = cRLSign, keyCertSign + +# Some might want this also +# nsCertType = sslCA, emailCA + +# Include email address in subject alt name: another PKIX recommendation +# subjectAltName=email:copy +# Copy issuer details +# issuerAltName=issuer:copy + +# DER hex encoding of an extension: beware experts only! +# obj=DER:02:03 +# Where 'obj' is a standard or added object +# You can even override a supported extension: +# basicConstraints= critical, DER:30:03:01:01:FF + +[ crl_ext ] + +# CRL extensions. +# Only issuerAltName and authorityKeyIdentifier make any sense in a CRL. + +# issuerAltName=issuer:copy +authorityKeyIdentifier=keyid:always + +[ proxy_cert_ext ] +# These extensions should be added when creating a proxy certificate + +# This goes against PKIX guidelines but some CAs do it and some software +# requires this to avoid interpreting an end user certificate as a CA. + +basicConstraints=CA:FALSE + +# Here are some examples of the usage of nsCertType. If it is omitted +# the certificate can be used for anything *except* object signing. + +# This is OK for an SSL server. +# nsCertType = server + +# For an object signing certificate this would be used. +# nsCertType = objsign + +# For normal client use this is typical +# nsCertType = client, email + +# and for everything including object signing: +# nsCertType = client, email, objsign + +# This is typical in keyUsage for a client certificate. +# keyUsage = nonRepudiation, digitalSignature, keyEncipherment + +# This will be displayed in Netscape's comment listbox. +nsComment = "OpenSSL Generated Certificate" + +# PKIX recommendations harmless if included in all certificates. +subjectKeyIdentifier=hash +authorityKeyIdentifier=keyid,issuer + +# This stuff is for subjectAltName and issuerAltname. +# Import the email address. +# subjectAltName=email:copy +# An alternative to produce certificates that aren't +# deprecated according to PKIX. +# subjectAltName=email:move + +# Copy subject details +# issuerAltName=issuer:copy + +#nsCaRevocationUrl = http://www.domain.dom/ca-crl.pem +#nsBaseUrl +#nsRevocationUrl +#nsRenewalUrl +#nsCaPolicyUrl +#nsSslServerName + +# This really needs to be in place for it to be a proxy certificate. +proxyCertInfo=critical,language:id-ppl-anyLanguage,pathlen:3,policy:foo + +#################################################################### +[ tsa ] + +default_tsa = tsa_config1 # the default TSA section + +[ tsa_config1 ] + +# These are used by the TSA reply generation only. +dir = ./demoCA # TSA root directory +serial = $dir/tsaserial # The current serial number (mandatory) +crypto_device = builtin # OpenSSL engine to use for signing +signer_cert = $dir/tsacert.pem # The TSA signing certificate + # (optional) +certs = $dir/cacert.pem # Certificate chain to include in reply + # (optional) +signer_key = $dir/private/tsakey.pem # The TSA private key (optional) + +default_policy = tsa_policy1 # Policy if request did not specify it + # (optional) +other_policies = tsa_policy2, tsa_policy3 # acceptable policies (optional) +digests = md5, sha1 # Acceptable message digests (mandatory) +accuracy = secs:1, millisecs:500, microsecs:100 # (optional) +clock_precision_digits = 0 # number of digits after dot. (optional) +ordering = yes # Is ordering defined for timestamps? + # (optional, default: no) +tsa_name = yes # Must the TSA name be included in the reply? + # (optional, default: no) +ess_cert_id_chain = no # Must the ESS cert id chain be included? + # (optional, default: no) diff --git a/proto/bgp/bgpsec/tests.c b/proto/bgp/bgpsec/tests.c new file mode 100644 index 00000000..7b35a950 --- /dev/null +++ b/proto/bgp/bgpsec/tests.c @@ -0,0 +1,287 @@ +#include "validate.h" +#include +#include + +#define HEADER(msg) printf("--------------- " msg "\n"); +#define HEADER1(msg, arg) printf("--------------- " msg "\n", arg); + +#define RESULT(test, is_success) { \ + printf("%7.7s: ", ((is_success) ? "ok" : "not ok")); \ + printf("%4d: ", __LINE__); \ + printf test; \ + printf("\n"); \ + if (is_success) good++; \ + else bad++; \ + } + +#define DIE(msg) do { fprintf(stderr, "CRITICAL FAIL: %s\n", msg); exit(1); } while(1); + +#define DUMMYCERTFILE "router-key.15708" + +#define TEST_KEY_REPO_PATH "/tmp/bgpsec-keys-testrepo" + +int main(int argc, char **argv) { + byte signature[1024]; + int signature_len = sizeof(signature); + char strBuffer[1024]; + bgpsec_key_data key_data; + char ski[1024]; + char filePrefix[MAXPATHLEN]; + char fileName[MAXPATHLEN]; + int signature_algorithms[] = { BGPSEC_ALGORITHM_SHA256_ECDSA_P_256, -1 }; + byte data_to_sign[] = { 1,2,3,4,5,6,7,8 }; + + struct bgp_config bgpconfig; + bgpconfig.bgpsec_key_repo_path = TEST_KEY_REPO_PATH; + bgpconfig.bgpsec_save_binary_keys = 1; + + BIGNUM newbignum; + EC_POINT *new_point; + + FILE *fp; + + printf("Testing:\n"); + + int good = 0, bad = 0; + + /* test whether we can sign a block of text */ + int ret; + int curveId; + int bin_save; + + for(bin_save = 0; bin_save < 2; bin_save++) { + HEADER1("starting test with binary_keys = %d", bin_save); + + int algorithm_count = 0; + bgpconfig.bgpsec_save_binary_keys = bin_save; + + + /* create a dummy certificate to use */ + system("../proto/bgp/bgpsec/gen-router-key -d " TEST_KEY_REPO_PATH " -c ../proto/bgp/bgpsec/router-key.cnf -p > ski.txt"); + fp = fopen("ski.txt", "r"); + if (NULL == fp) + DIE("failed to open the ski.txt file that should have been created"); + ski[sizeof(ski)-1] = '\0'; + if (NULL == fgets(ski, sizeof(ski)-1, fp)) + DIE("Couldn't read the SKI from the ski.txt file"); + ski[strlen(ski)-1] = '\0'; /* chomp the LF off */ + fclose(fp); + + /* now define all the file names based on it the new cert */ + filePrefix[sizeof(filePrefix)-1] = '\0'; + snprintf(filePrefix, sizeof(filePrefix)-1, "%s/%s", TEST_KEY_REPO_PATH, ski); + generate_ski_filename(filePrefix, sizeof(filePrefix), TEST_KEY_REPO_PATH, + ski, strlen(ski)); + + while(signature_algorithms[algorithm_count] > 0) { + HEADER1("-- starting test with sig alg = %d", + signature_algorithms[algorithm_count]); + bgpsec_key_data key_data; + curveId = signature_algorithms[algorithm_count]; + + ret = bgpsec_load_key(&bgpconfig, filePrefix, &key_data, + curveId, 1); + RESULT(("cert sign: loaded the router key from tmp file: %s", + filePrefix), + ret == BGPSEC_SUCCESS); + + + /* generate a signature using a certificate */ + signature_len = + bgpsec_sign_data_with_key(&bgpconfig, + data_to_sign, sizeof(data_to_sign), + key_data, + signature_algorithms[algorithm_count], + signature, sizeof(signature)); + + RESULT(("cert sign: algorithm %d, signature length (%d) is not negative", + signature_algorithms[algorithm_count], signature_len), + signature_len > -1); + RESULT(("cert sign: algorithm %d, signature length (%d) has at least a byte", signature_algorithms[algorithm_count], signature_len), signature_len > 0); + + /* modify the private key so it can't be part of the verification */ + BN_init(&newbignum); + EC_KEY_set_private_key(key_data.ecdsa_key, &newbignum); + + /* verify that the signature matches */ + ret = bgpsec_verify_signature_with_key(&bgpconfig, data_to_sign, + sizeof(data_to_sign), + key_data, + signature_algorithms[algorithm_count], + signature, signature_len); + RESULT(("cert sign: verify signature result: %d (should be %d)", + ret, BGPSEC_SIGNATURE_MATCH), + ret == BGPSEC_SIGNATURE_MATCH); + + /* verify that the signature matches */ + ret = bgpsec_verify_signature_with_key(&bgpconfig, data_to_sign, + sizeof(data_to_sign), + key_data, + signature_algorithms[algorithm_count], + signature, signature_len); + RESULT(("cert sign: verify signature result2: %d (should be %d)", + ret, BGPSEC_SIGNATURE_MATCH), + ret == BGPSEC_SIGNATURE_MATCH); + + /* modify the public key so it can't be part of the verification */ + /* (which should make the verification fail now) */ + new_point = EC_POINT_new(EC_GROUP_new_by_curve_name(curveId)); + EC_KEY_set_public_key(key_data.ecdsa_key, new_point); + EC_POINT_free(new_point); + + /* verify that the signature no longer matches */ + ret = bgpsec_verify_signature_with_key(&bgpconfig, + data_to_sign, + sizeof(data_to_sign), + key_data, + signature_algorithms[algorithm_count], + signature, signature_len); + RESULT(("cert sign: verify signature fail result: %d (should be %d)", + ret, BGPSEC_SIGNATURE_MISMATCH), + ret == BGPSEC_SIGNATURE_MISMATCH); + + /* completely get rid of the current key */ + EC_KEY_free(key_data.ecdsa_key); + key_data.ecdsa_key = NULL; + + /* now reload the key from the files and use them to verify it */ + /* NOTE: this should reload the previously saved binary key */ + ret = bgpsec_load_key(&bgpconfig, filePrefix, &key_data, curveId, 1); + RESULT(("cert sign: loading key function returned: %d (should be %d)", + ret, BGPSEC_SUCCESS), ret == BGPSEC_SUCCESS); + + /* verify that the signature matches again with the loaded key */ + ret = bgpsec_verify_signature_with_key(&bgpconfig, + data_to_sign, + sizeof(data_to_sign), + key_data, + signature_algorithms[algorithm_count], + signature, signature_len); + RESULT(("cert sign: verify signature result of generated bin key: %d (should be %d)", + ret, BGPSEC_SIGNATURE_MATCH), + ret == BGPSEC_SIGNATURE_MATCH); + + + /* Nuke the binary version of the key, and make sure the x.509 + gets reloaded again */ + snprintf(fileName, sizeof(fileName), "%s.bin_pub", filePrefix); + unlink(fileName); + + ret = bgpsec_load_key(&bgpconfig, filePrefix, &key_data, curveId, 1); + RESULT(("cert sign: loading key function returned: %d (should be %d)", + ret, BGPSEC_SUCCESS), ret == BGPSEC_SUCCESS); + + /* verify that the signature matches again with the loaded key */ + ret = bgpsec_verify_signature_with_key(&bgpconfig, + data_to_sign, + sizeof(data_to_sign), + key_data, + signature_algorithms[algorithm_count], + signature, signature_len); + RESULT(("cert sign: verify signature result of non-bin: %d (should be %d)", + ret, BGPSEC_SIGNATURE_MATCH), + ret == BGPSEC_SIGNATURE_MATCH); + + + + /* completely get rid of the current key */ + EC_KEY_free(key_data.ecdsa_key); + key_data.ecdsa_key = NULL; + + /* now reload just the public part of the key and test just it */ + ret = bgpsec_load_key(&bgpconfig, filePrefix, &key_data, curveId, 0); + RESULT(("cert sign: loading public key function returned: %d (should be %d)", + ret, BGPSEC_SUCCESS), ret == BGPSEC_SUCCESS); + + /* verify that the signature matches again with the public key */ + ret = bgpsec_verify_signature_with_key(&bgpconfig, + data_to_sign, + sizeof(data_to_sign), + key_data, + signature_algorithms[algorithm_count], + signature, signature_len); + RESULT(("cert sign: verify (pub) signature result: %d (should be %d)", + ret, BGPSEC_SIGNATURE_MATCH), + ret == BGPSEC_SIGNATURE_MATCH); + + /* generate a signature using a fingerprint */ + /* XXX: set test directory to search for matching ski->certs */ + signature_len = + bgpsec_sign_data_with_ascii_ski(&bgpconfig, + data_to_sign, + sizeof(data_to_sign), + ski, strlen(ski)+1, + signature_algorithms[algorithm_count], + signature, sizeof(signature)); + + RESULT(("ski sign: algorithm %d, signature length (%d) is not negative", + signature_algorithms[algorithm_count], signature_len), + signature_len > -1); + RESULT(("ski sign: algorithm %d, signature length (%d) has at least a byte", signature_algorithms[algorithm_count], signature_len), signature_len > 0); + + + /* verify that the signature matches */ + ret = bgpsec_verify_signature_with_ascii_ski(&bgpconfig, + data_to_sign, + sizeof(data_to_sign), + ski, strlen(ski)+1, + signature_algorithms[algorithm_count], + signature, sizeof(signature)); + RESULT(("ski sign: verify signature result: %d (should be %d)", + ret, BGPSEC_SIGNATURE_MATCH), + ret == BGPSEC_SIGNATURE_MATCH); + + if (bin_save) { + /* Nuke the x.509 certificate version of the key, and make + sure the binary version can be loaded by itself. */ + snprintf(fileName, sizeof(fileName), "%s.pub", filePrefix); + unlink(fileName); + snprintf(fileName, sizeof(fileName), "%s.private", filePrefix); + unlink(fileName); + + + /* verify that the signature matches with an ski */ + ret = bgpsec_verify_signature_with_ascii_ski(&bgpconfig, + data_to_sign, + sizeof(data_to_sign), + ski, strlen(ski)+1, + signature_algorithms[algorithm_count], + signature, sizeof(signature)); + + RESULT(("ski sign: verify signature result of binary only: %d (should be %d)", + ret, BGPSEC_SIGNATURE_MATCH), + ret == BGPSEC_SIGNATURE_MATCH); + + + /* verify that the signature matches with the key */ + + ret = bgpsec_load_key(&bgpconfig, filePrefix, &key_data, curveId, 1); + RESULT(("cert sign: loading binary-only key function returned: %d (should be %d)", + ret, BGPSEC_SUCCESS), ret == BGPSEC_SUCCESS); + + /* verify that the signature matches again with the loaded key */ + ret = bgpsec_verify_signature_with_key(&bgpconfig, + data_to_sign, + sizeof(data_to_sign), + key_data, + signature_algorithms[algorithm_count], + signature, signature_len); + RESULT(("cert sign: verify signature result of binary-only: %d (should be %d)", + ret, BGPSEC_SIGNATURE_MATCH), + ret == BGPSEC_SIGNATURE_MATCH); + } + + + + /* move on to the next algorithm */ + algorithm_count++; + } + + } + + printf("\nResults:\n"); + printf(" Good: %d\n", good); + printf(" Bad: %d\n", bad); + + return 0; +} diff --git a/proto/bgp/bgpsec/validate.c b/proto/bgp/bgpsec/validate.c new file mode 100644 index 00000000..f97369eb --- /dev/null +++ b/proto/bgp/bgpsec/validate.c @@ -0,0 +1,253 @@ +/* + * bgpsec: validation functions + * + * + * Parsons, Inc. + * (c) 2013-2013 + * + * Code can be used under either license: + * - Freely distributed and used under the terms of the GNU GPLv2. + * - Freely distributed and used under a BSD license, See README.bgpsec. + */ + +#include +#include +#include +#include +#include + +#include +#include +#include "validate.h" + +static int +convert_ski_to_ascii(const byte *ski, const size_t ski_len, + char *ascii_ski, size_t ascii_ski_len) { + + int i; + + if (ski_len * 2 + 1 >= ascii_ski_len) { + log(L_ERR "validate: buffer to small for SKI length: %d", ski_len); + return BGPSEC_FAILURE; + } + + memset(ascii_ski, 0, ascii_ski_len); + + for (i = 0; i < ski_len; i++) + sprintf(ascii_ski + 2 * i, "%02X", ski[i]); + + return BGPSEC_SUCCESS; +} + +static int +bgpsec_load_key_internal(const struct bgp_config *conf, + const char *filename, + bgpsec_key_data *key_data, + EVP_PKEY *(*d2i_bio_method)(BIO *, EVP_PKEY **)) { + int ret = BGPSEC_FAILURE; + BIO *bio = NULL; + + if ((bio = BIO_new_file(filename, "rb")) != NULL && + d2i_bio_method(bio, &key_data->pkey) != NULL && + EVP_PKEY_id(key_data->pkey) == EVP_PKEY_EC && + BIO_free(bio)) + { + EC_KEY_set_asn1_flag(EVP_PKEY_get0(key_data->pkey), OPENSSL_EC_NAMED_CURVE); + ret = BGPSEC_SUCCESS; + bio = NULL; + } + + BIO_free(bio); + + return ret; +} + +int +bgpsec_load_private_key(const struct bgp_config *conf, + const char *filename, + bgpsec_key_data *key_data) { + return bgpsec_load_key_internal(conf, filename, key_data, d2i_PrivateKey_bio); +} + +int +bgpsec_load_public_key(const struct bgp_config *conf, + const char *filename, + bgpsec_key_data *key_data) { + return bgpsec_load_key_internal(conf, filename, key_data, d2i_PUBKEY_bio); +} + +/* Might need to call OpenSSL_add_all_digests() somewhere */ + +int bgpsec_sign_data_with_key(const struct bgp_config *conf, + const byte *octets, const size_t octets_len, + const bgpsec_key_data key, + const int signature_algorithm, + byte *signature, size_t signature_len) { + + EVP_PKEY_CTX *ctx = NULL; + unsigned char md_value[EVP_MAX_MD_SIZE]; + size_t md_len; + size_t sig_len = signature_len; + int result = -1; + + switch (signature_algorithm) { + case BGPSEC_ALGORITHM_SHA256_ECDSA_P_256: + + if (EVP_Digest(octets, octets_len, md_value, (unsigned int *)&md_len, + EVP_sha256(), NULL) && + (ctx = EVP_PKEY_CTX_new(key.pkey, NULL)) != NULL && + EVP_PKEY_sign_init(ctx) > 0 && + EVP_PKEY_CTX_set_signature_md(ctx, EVP_sha256()) > 0 && + EVP_PKEY_sign(ctx, signature, &sig_len, md_value, md_len) > 0) + { + result = sig_len; + } + else + { + log(L_ERR "validate: Failed to create digest/sign"); + return BGPSEC_FAILURE; + } + + default: + break; + } + + EVP_PKEY_CTX_free(ctx); + return result; +} + +int bgpsec_sign_data_with_ascii_ski(const struct bgp_config *conf, + const byte *octets, const size_t octets_len, + const char *ski, const size_t ski_len, + const int asn, + const int signature_algorithm, + byte *signature, size_t signature_len) { + const char *rootPath = (conf && conf->bgpsec_priv_key_path) ? conf->bgpsec_priv_key_path : DEFAULT_PRIV_KEY_PATH; + bgpsec_key_data key = { NULL }; + char filename[MAXPATHLEN]; + + if (snprintf(filename, sizeof(filename), "%s/%d.%s.key", rootPath, asn, ski) >= sizeof(filename) || + bgpsec_load_private_key(conf, filename, &key) != BGPSEC_SUCCESS) + { + return BGPSEC_FAILURE; + } + + return bgpsec_sign_data_with_key(conf, octets, octets_len, key, + signature_algorithm, signature, signature_len); +} + +int bgpsec_sign_data_with_bin_ski(const struct bgp_config *conf, + const byte *octets, const size_t octets_len, + const byte *ski, const size_t ski_len, + const int asn, + const int signature_algorithm, + byte *signature, size_t signature_len) { + char ascii_ski[MAXPATHLEN]; + + if (convert_ski_to_ascii(ski, ski_len, ascii_ski, sizeof(ascii_ski)) == BGPSEC_FAILURE) + return BGPSEC_FAILURE; + + return bgpsec_sign_data_with_ascii_ski(conf, octets, octets_len, ascii_ski, sizeof(ascii_ski), + asn, signature_algorithm, signature, signature_len); +} + +int bgpsec_verify_signature_with_key(const struct bgp_config *conf, + const byte *octets, const size_t octets_len, + const bgpsec_key_data key, + const int signature_algorithm, + const byte *signature, const size_t signature_len) { + EVP_PKEY_CTX *ctx = NULL; + unsigned char md_value[EVP_MAX_MD_SIZE]; + size_t md_len; + int result = BGPSEC_SIGNATURE_ERROR; + + switch (signature_algorithm) { + case BGPSEC_ALGORITHM_SHA256_ECDSA_P_256: + + if (EVP_Digest(octets, octets_len, md_value, (unsigned int *)&md_len, + EVP_sha256(), NULL) && + (ctx = EVP_PKEY_CTX_new(key.pkey, NULL)) != NULL && + EVP_PKEY_verify_init(ctx) > 0 && + EVP_PKEY_CTX_set_signature_md(ctx, EVP_sha256()) > 0 && + EVP_PKEY_verify(ctx, signature, signature_len, md_value, md_len) > 0) + { + result = BGPSEC_SIGNATURE_MATCH; + } + +#if 0 && defined(LOG_OPENSSL_ERRORS) && defined(LOG_TO_STDERR) + else + ERR_print_errors_fp(stderr); +#endif + + default: + break; + } + + EVP_PKEY_CTX_free(ctx); + return result; +} + +int bgpsec_verify_signature_with_ascii_ski(const struct bgp_config *conf, + const byte *octets, const size_t octets_len, + const char *ski, const size_t ski_len, + const int asn, + const int signature_algorithm, + const byte *signature, const size_t signature_len) { + const char *rootPath = (conf && conf->bgpsec_key_repo_path) ? conf->bgpsec_key_repo_path : DEFAULT_KEY_REPO_PATH; + bgpsec_key_data key = { NULL }; + char filename[MAXPATHLEN]; + int n; + + for (n = 0; n < BGPSEC_MAX_SKI_COLLISIONS; n++) { + + if (snprintf(filename, sizeof(filename), "%s/%d.%s.%d.key", rootPath, asn, ski, n) >= sizeof(filename)) + break; + + if (bgpsec_load_public_key(conf, filename, &key) != BGPSEC_SUCCESS) + break; + + if (bgpsec_verify_signature_with_key(conf, octets, octets_len, key, signature_algorithm, + signature, signature_len) == BGPSEC_SIGNATURE_MATCH) + return BGPSEC_SIGNATURE_MATCH; + + EVP_PKEY_free(key.pkey); + key.pkey = NULL; + } + + return BGPSEC_SIGNATURE_ERROR; +} + +int bgpsec_verify_signature_with_bin_ski(const struct bgp_config *conf, + const byte *octets, const size_t octets_len, + const byte *ski, const size_t ski_len, + const int asn, + const int signature_algorithm, + const byte *signature, const size_t signature_len) { + char ascii_ski[MAXPATHLEN]; + + if (convert_ski_to_ascii(ski, ski_len, ascii_ski, sizeof(ascii_ski)) == BGPSEC_FAILURE) + return BGPSEC_SIGNATURE_ERROR; + + return bgpsec_verify_signature_with_ascii_ski(conf, octets, octets_len, ascii_ski, sizeof(ascii_ski), + asn, signature_algorithm, signature, signature_len); +} + +int bgpsec_calculate_ski(const bgpsec_key_data key, + byte *ski, const size_t ski_len) { + X509_PUBKEY *pubkey = NULL; + byte digest[EVP_MAX_MD_SIZE]; + int result = BGPSEC_FAILURE; + unsigned digest_len; + + if (X509_PUBKEY_set(&pubkey, key.pkey) && + EVP_Digest(pubkey->public_key->data, pubkey->public_key->length, + digest, &digest_len, EVP_sha1(), NULL) && + digest_len <= ski_len) + { + memcpy(ski, digest, digest_len); + result = digest_len; + } + + X509_PUBKEY_free(pubkey); + return result; +} diff --git a/proto/bgp/bgpsec/validate.h b/proto/bgp/bgpsec/validate.h new file mode 100644 index 00000000..23fd886e --- /dev/null +++ b/proto/bgp/bgpsec/validate.h @@ -0,0 +1,159 @@ +/* + * bgpsec: validation functions + * + * + * Parsons, Inc. + * (c) 2013-2013 + * + * Code can be used under either license: + * - Freely distributed and used under the terms of the GNU GPLv2. + * - Freely distributed and used under a BSD license, See README.bgpsec. + */ + +#ifndef _BIRD_VALIDATE_H_ +#define _BIRD_VALIDATE_H_ + +#include +#include + +#include "nest/route.h" +#include "../bgp.h" + +/* XXX: these need to be configurable in the bird config file instead */ +#define DEFAULT_KEY_REPO_PATH "/usr/share/bird/bgpsec-keys" +#define DEFAULT_PRIV_KEY_PATH "/usr/share/bird/bgpsec-private-keys" +#define BGPSEC_MAX_SKI_COLLISIONS 3 + +/* + * Structure to store keying data in. This used to be a union, but + * since we should be using EVP_PKEY everywhere it's now just a wrapper. + */ +typedef struct { + EVP_PKEY *pkey; +} bgpsec_key_data; + +/* Generic error codes */ +#define BGPSEC_SUCCESS 0 +#define BGPSEC_FAILURE -1 + + +/* These match the defined algorithm bytes from the protocol definition */ + +/* Algorithm #: 1 + * Digest algorithm: SHA-256 + * Signature algorithm: ECDSA P-256 + */ +/* XXX: IANA has yet to assign this number; 1 is a logical guess */ +/* XXX: Definiton in draft-turner-sidr-bgpsec-algs-00.txt */ +#define BGPSEC_ALGORITHM_SHA256_ECDSA_P_256 1 + +#define BGPSEC_DEFAULT_CURVE BGPSEC_ALGORITHM_SHA256_ECDSA_P_256 + +/* + * Signs a blob of octets in 'octets' with the certificate found using + * the 'subject_key_ident' using the algorithm indicated by + * 'signature_algorithm'. The resulting signature is placed in the + * pre-allocated 'signature' block, whose pre-allocated length must be + * stored in 'signature_len'. + * + * Internally this looks up the certificate and then calls + * bgpsec_sign_data_with_key(), defined below. + * + * Returns: The length of the signature actually created, or -1 on error. + */ +int bgpsec_sign_data_with_ascii_ski(const struct bgp_config *conf, + const byte *octets, const size_t octets_len, + const char *ski, const size_t ski_len, + const int asn, + const int signature_algorithm, + byte *signature, size_t signature_len); + +int bgpsec_sign_data_with_bin_ski(const struct bgp_config *conf, + const byte *octets, const size_t octets_len, + const byte *ski, const size_t ski_len, + const int asn, + const int signature_algorithm, + byte *signature, size_t signature_len); + +/* + * Signs a blob of octets in 'octets' with the private key 'key' using + * the algorithm indicated by 'signature_algorithm'. The resulting signature + * is placed in the pre-allocated 'signature' block, who's + * pre-allocated length bust be stored in 'signature_len'. + * + * Returns: The length of the signature actually created, or -1 on error. + */ +int bgpsec_sign_data_with_key(const struct bgp_config *conf, + const byte *octets, const size_t octets_len, + const bgpsec_key_data key, + const int signature_algorithm, + byte *signature, size_t signature_len); + + +#define BGPSEC_SIGNATURE_MATCH 0 +#define BGPSEC_SIGNATURE_ERROR 1 +/* + * Validates a signature on a block and returns an error code if the + * signature dosen't match. The data to check the signature for + * should be in 'octets' with length 'octets_len', and the public key + * to check with should be in 'key' using algorithm + * 'signature_algorithm'. The signature from the bgp packet should + * should be in 'signature' with length 'signature_len'. + * + * Returns: + * Success: BGPSEC_SIGNATURE_MATCH + * Failure: BGPSEC_SIGNATURE_ERROR + */ +int bgpsec_verify_signature_with_key(const struct bgp_config *conf, + const byte *octets, const size_t octets_len, + const bgpsec_key_data key, + const int signature_algorithm, + const byte *signature, const size_t signature_len); + +/* verifies a signature when passed an ascii SKI */ +int bgpsec_verify_signature_with_ascii_ski(const struct bgp_config *conf, + const byte *octets, const size_t octets_len, + const char *ski, const size_t ski_len, + const int asn, + const int signature_algorithm, + const byte *signature, const size_t signature_len); + +/* verifies a signature when passed a binary SKI + (internally, this is a wrapper around the above function and merely + prints the binary to an hex-encoded ascii first) */ +int bgpsec_verify_signature_with_bin_ski(const struct bgp_config *conf, + const byte *octets, const size_t octets_len, + const byte *ski, const size_t ski_len, + const int asn, + const int signature_algorithm, + const byte *signature, const size_t signature_len); + + +/* + * Load private and public keys from files. + * + * Returns: + * Success: BGPSEC_SUCCESS + * Failure: BGPSEC_FAILURE + */ + +int bgpsec_load_private_key(const struct bgp_config *conf, + const char *filename, + bgpsec_key_data *key_data); + +int bgpsec_load_public_key(const struct bgp_config *conf, + const char *filename, + bgpsec_key_data *key_data); + +/* + * Calculate the SKI of a key. + * + * Returns: + * Success: length of calculated SKI + * Failure: BGPSEC_FAILURE + */ + +int bgpsec_calculate_ski(const bgpsec_key_data key, + byte *ski, const size_t ski_len); + +#endif diff --git a/proto/bgp/config.Y b/proto/bgp/config.Y index 49afe5ae..47713a87 100644 --- a/proto/bgp/config.Y +++ b/proto/bgp/config.Y @@ -4,6 +4,14 @@ * (c) 2000 Martin Mares * * Can be freely distributed and used under the terms of the GNU GPL. + * + * + * Code added from Parsons, Inc. (BGPSEC additions) + * (c) 2013-2013 + * + * Can be used under either license: + * - Freely distributed and used under the terms of the GNU GPLv2. + * - Freely distributed and used under a BSD license, See README.bgpsec. */ CF_HDR @@ -27,7 +35,10 @@ CF_KEYWORDS(BGP, LOCAL, NEIGHBOR, AS, HOLD, TIME, CONNECT, RETRY, INTERPRET, COMMUNITIES, BGP_ORIGINATOR_ID, BGP_CLUSTER_LIST, IGP, TABLE, GATEWAY, DIRECT, RECURSIVE, MED, TTL, SECURITY, DETERMINISTIC, SECONDARY, ALLOW, BFD, ADD, PATHS, RX, TX, GRACEFUL, RESTART, AWARE, - CHECK, LINK, PORT) + CHECK, LINK, PORT, + BGPSEC, BGPSEC_SKI, BGPSEC_KEY_REPO_PATH, BGPSEC_PRIV_KEY_PATH, + BGPSEC_SAVE_BINARY_KEYS, BGPSEC_PREFER, BGPSEC_NO_PCOUNT0, + BGPSEC_REQUIRE, BGPSEC_NO_INVALID_ROUTES) CF_GRAMMAR @@ -48,6 +59,15 @@ bgp_proto_start: proto_start BGP { BGP_CFG->error_delay_time_max = 300; BGP_CFG->enable_refresh = 1; BGP_CFG->enable_as4 = 1; + BGP_CFG->enable_bgpsec = 1; + BGP_CFG->bgpsec_ski = 0; + BGP_CFG->bgpsec_priv_key_path = 0; + BGP_CFG->bgpsec_key_repo_path = 0; + BGP_CFG->bgpsec_prefer = 1; + BGP_CFG->bgpsec_no_pcount0 = 1; + BGP_CFG->bgpsec_no_invalid_routes = 0; + BGP_CFG->bgpsec_require = 0; + BGP_CFG->bgpsec_save_binary_keys = 0; BGP_CFG->capabilities = 2; BGP_CFG->advertise_ipv4 = 1; BGP_CFG->interpret_communities = 1; @@ -130,6 +150,14 @@ bgp_proto: | bgp_proto GRACEFUL RESTART TIME expr ';' { BGP_CFG->gr_time = $5; } | bgp_proto IGP TABLE rtable ';' { BGP_CFG->igp_table = $4; } | bgp_proto TTL SECURITY bool ';' { BGP_CFG->ttl_security = $4; } + | bgp_proto BGPSEC bool ';' { BGP_CFG->enable_bgpsec = $3; } + | bgp_proto BGPSEC_PREFER bool ';' { BGP_CFG->bgpsec_prefer = $3; } + | bgp_proto BGPSEC_NO_INVALID_ROUTES bool ';' { BGP_CFG->bgpsec_no_invalid_routes = $3; } + | bgp_proto BGPSEC_SKI text ';' { BGP_CFG->bgpsec_ski = $3; } + | bgp_proto BGPSEC_KEY_REPO_PATH text ';' { BGP_CFG->bgpsec_key_repo_path = $3; } + | bgp_proto BGPSEC_PRIV_KEY_PATH text ';' { BGP_CFG->bgpsec_priv_key_path = $3; } + | bgp_proto BGPSEC_SAVE_BINARY_KEYS bool ';' { BGP_CFG->bgpsec_save_binary_keys = $3; } + | bgp_proto BGPSEC_NO_PCOUNT0 bool ';' { BGP_CFG->bgpsec_no_pcount0 = $3; } | bgp_proto CHECK LINK bool ';' { BGP_CFG->check_link = $4; } | bgp_proto BFD bool ';' { BGP_CFG->bfd = $3; cf_check_bfd($3); } ; diff --git a/proto/bgp/packets.c b/proto/bgp/packets.c index 4bd68f52..a867a2f0 100644 --- a/proto/bgp/packets.c +++ b/proto/bgp/packets.c @@ -4,7 +4,14 @@ * (c) 2000 Martin Mares * * Can be freely distributed and used under the terms of the GNU GPL. - */ + * + * + * Code added from Parsons, Inc. (BGPSEC additions) + * (c) 2013-2013 + * + * Can be used under either license: + * - Freely distributed and used under the terms of the GNU GPLv2. + * - Freely distributed and used under a BSD license, See README.bgpsec. */ #undef LOCAL_DEBUG @@ -21,6 +28,7 @@ #include "nest/cli.h" #include "bgp.h" +#undef sk_new /* remove the bird-specific compatability wrapper */ #define BGP_RR_REQUEST 0 @@ -38,6 +46,13 @@ static byte fsm_err_subcode[BS_MAX] = { [BS_ESTABLISHED] = 3 }; +/* convert from AF_INET address family to BGP AF AF */ +u16 af_to_bgp_af(int af) { + if (af == AF_INET) { return BGP_AF_IPV4; } + else if (af == AF_INET6) { return BGP_AF_IPV6; } + else { return 0; } +} + /* * MRT Dump format is not semantically specified. * We will use these values in appropriate fields: @@ -73,7 +88,7 @@ mrt_put_bgp4_hdr(byte *buf, struct bgp_conn *conn, int as4) } put_u16(buf+0, (p->neigh && p->neigh->iface) ? p->neigh->iface->index : 0); - put_u16(buf+2, BGP_AF); + put_u16(buf+2, af_to_bgp_af(conn->sk->af) ); buf+=4; buf = put_ipa(buf, conn->sk ? conn->sk->daddr : IPA_NONE); buf = put_ipa(buf, conn->sk ? conn->sk->saddr : IPA_NONE); @@ -200,6 +215,35 @@ bgp_put_cap_as4(struct bgp_proto *p, byte *buf) return buf + 4; } +#ifdef CONFIG_BGPSEC +/* Note: this adds 2 capabilities to bgp capabilitiies. + * One indicates this router can send BGPSEC messages and + * the other indicating it can recieve BGPSEC messages + * + * Currently, this code doesn't have differentiate in its + * configuration. It either supports sending/receiving BGPSEC + * messages or doesn't support BGPSEC */ +static byte * +bgp_put_cap_bgpsec(struct bgp_conn *conn UNUSED, byte *buf) +{ + /* can send bgpsec capability */ + *buf++ = BGPSEC_CAPABILITY; /* XXX Capability 72: best guess, BGPSEC */ + *buf++ = 3; /* BGPSEC Capability length */ + /* bgpsec version and capable of sending */ + *buf++ = ( (BGPSEC_VERSION << 4) | 0x08 ); + put_u16(buf, BGP_AF); /* address family */ + buf = buf + 2; + + /* can receive bgpsec capability */ + *buf++ = BGPSEC_CAPABILITY; /* XXX Capability 72: best guess, BGPSEC */ + *buf++ = 3; /* BGPSEC Capability length */ + /* bgpsec version and capable of receiving bgpsec */ + *buf++ = ( (BGPSEC_VERSION << 4) | 0x00 ); + put_u16(buf, BGP_AF); /* address family */ + return buf + 2; +} +#endif + static byte * bgp_put_cap_add_path(struct bgp_proto *p, byte *buf) { @@ -271,6 +315,14 @@ bgp_create_open(struct bgp_conn *conn, byte *buf) if (p->cf->add_path) cap = bgp_put_cap_add_path(p, cap); +#ifdef CONFIG_BGPSEC + /* xxx */ + BGP_TRACE(D_PACKETS, "Add BGPSec capability? \'%d\', v%d, as4:%d", + p->cf->enable_bgpsec, BGPSEC_VERSION, p->cf->enable_as4); + if (p->cf->enable_bgpsec) + cap = bgp_put_cap_bgpsec(conn, cap); +#endif + if (p->cf->enable_refresh) cap = bgp_put_cap_err(p, cap); @@ -335,7 +387,10 @@ bgp_flush_prefixes(struct bgp_proto *p, struct bgp_bucket *buck) } } -#ifndef IPV6 /* IPv4 version */ + + +#if !defined(IPV6) && !defined(CONFIG_BGPSEC) /* IPv4 version */ + static byte * bgp_create_update(struct bgp_conn *conn, byte *buf) @@ -371,7 +426,7 @@ bgp_create_update(struct bgp_conn *conn, byte *buf) } DBG("Processing bucket %p\n", buck); - a_size = bgp_encode_attrs(p, w+2, buck->eattrs, 2048); + a_size = bgp_encode_attrs(p, w+2, buck->eattrs, 2048, buck); if (a_size < 0) { @@ -413,7 +468,9 @@ bgp_create_end_mark(struct bgp_conn *conn, byte *buf) return buf+4; } -#else /* IPv6 version */ + +#else /* IPv6 or BGPSEC version */ + static inline int same_iface(struct bgp_proto *p, ip_addr *ip) @@ -431,7 +488,7 @@ bgp_create_update(struct bgp_conn *conn, byte *buf) int remains = BGP_MAX_PACKET_LENGTH - BGP_HEADER_LENGTH - 4; byte *w, *w_stored, *tmp, *tstart; ip_addr *ipp, ip, ip_ll; - ea_list *ea; + ea_list *ea = NULL; eattr *nh; put_u16(buf, 0); @@ -442,14 +499,18 @@ bgp_create_update(struct bgp_conn *conn, byte *buf) DBG("Withdrawn routes:\n"); tmp = bgp_attach_attr_wa(&ea, bgp_linpool, BA_MP_UNREACH_NLRI, remains-8); *tmp++ = 0; +#ifdef IPV6 *tmp++ = BGP_AF_IPV6; +#else + *tmp++ = BGP_AF_IPV4; +#endif *tmp++ = 1; ea->attrs[0].u.ptr->length = 3 + bgp_encode_prefixes(p, tmp, buck, remains-11); size = bgp_encode_attrs(p, w, ea, remains); ASSERT(size >= 0); w += size; remains -= size; - } + } if (remains >= 3072) { @@ -468,6 +529,7 @@ bgp_create_update(struct bgp_conn *conn, byte *buf) w_stored = w; size = bgp_encode_attrs(p, w, buck->eattrs, 2048); + if (size < 0) { log(L_ERR "%s: Attribute list too long, skipping corresponding routes", p->p.name); @@ -536,9 +598,12 @@ bgp_create_update(struct bgp_conn *conn, byte *buf) } tstart = tmp = bgp_attach_attr_wa(&ea, bgp_linpool, BA_MP_REACH_NLRI, remains-8); - *tmp++ = 0; - *tmp++ = BGP_AF_IPV6; - *tmp++ = 1; + *tmp++ = 0; /* high order byte of AFI */ + +#ifdef IPV6 + *tmp++ = BGP_AF_IPV6; /* AFI */ + *tmp++ = 1; /* SAFI */ + if (ipa_is_link_local(ip)) ip = IPA_NONE; @@ -559,13 +624,39 @@ bgp_create_update(struct bgp_conn *conn, byte *buf) memcpy(tmp, &ip, 16); tmp += 16; } +#else + *tmp++ = BGP_AF_IPV4; /* AFI */ + *tmp++ = 1; /* SAFI */ + *tmp++ = 4; /* next hop length */ + ipa_hton(ip); /* next hop */ + memcpy(tmp, &ip, 4); + tmp += 4; +#endif - *tmp++ = 0; /* No SNPA information */ + *tmp++ = 0; /* reserved byte (No SNPA information) */ + byte *nlri = tmp; tmp += bgp_encode_prefixes(p, tmp, buck, remains - (8+3+32+1)); ea->attrs[0].u.ptr->length = tmp - tstart; + size = bgp_encode_attrs(p, w, ea, remains); + ASSERT(size >= 0); w += size; + remains -= size; + +#ifdef CONFIG_BGPSEC + if (p->conn->peer_bgpsec_support) { + int bgpsec_len = encode_bgpsec_attr(p->conn, buck->eattrs, w, remains, nlri); + + if ( bgpsec_len < 0 ) { + log(L_ERR "encode_bgpsec_attrs: bgpsec signing failed"); + return NULL; + } + w += bgpsec_len; + remains -= bgpsec_len; + } +#endif + break; } } @@ -798,8 +889,12 @@ bgp_tx(sock *sk) void bgp_parse_capabilities(struct bgp_conn *conn, byte *opt, int len) { - // struct bgp_proto *p = conn->bgp; - int i, cl; + struct bgp_proto *p = conn->bgp; /* used in BGP_TRACE */ + int i,cl; +#ifdef CONFIG_BGPSEC + u16 afi; + u8 safi; +#endif while (len > 0) { @@ -840,6 +935,53 @@ bgp_parse_capabilities(struct bgp_conn *conn, byte *opt, int len) conn->advertised_as = get_u32(opt + 2); break; +#ifdef CONFIG_BGPSEC + case BGPSEC_CAPABILITY: /* BGPSEC_CAPABILITY value currently arbitrary */ + if (cl != 3) /* data length must be 3 */ + goto err; + + if ( ! conn->bgp->cf->enable_bgpsec ) { + BGP_TRACE(D_PACKETS, "Error: bgp_parse_capabilities: BGPSEC NOT enabled locally"); + goto err; + } + + if ( BGPSEC_VERSION == (opt[2] & 0xF0) ) { + BGP_TRACE(D_PACKETS, "bgp_parse_capabilities: sender BGPSEC_VERSION matches : %d", (opt[2] & 0x0F)); + conn->peer_bgpsec_support = 1; + } + else { + BGP_TRACE(D_PACKETS, "Error: bgp_parse_capabilities: BGPSEC_VERSION does not match, loc : %d, rem : $d", BGPSEC_VERSION, (opt[2] & 0x0F)); + goto err; + } + + if (opt[2] & 0x08) { + BGP_TRACE(D_PACKETS, "bgp_parse_capabilities: sender can send BGPSEC messages : %d", opt[2]); + conn->bgp->bgpsec_send = 1; + } + + if (0 == (opt[2] & 0x08)) { + BGP_TRACE(D_PACKETS, "bgp_parse_capabilities: sender can receive BGPSEC messages : %d", opt[2]); + conn->bgp->bgpsec_receive = 1; + } + + afi = get_u16(opt + 3); + + if (BGP_AF_IPV4 == afi) { + BGP_TRACE(D_PACKETS, "bgp_parse_capabilities: sender using bgpsec IPV4 Address Family : %d", afi); + conn->bgp->bgpsec_ipv4 = 1; + } + else if (BGP_AF_IPV6 == afi) { + BGP_TRACE(D_PACKETS, "bgp_parse_capabilities: sender using IPV6 Address Family : %d", afi); + conn->bgp->bgpsec_ipv6 = 1; + } + else { + BGP_TRACE(D_PACKETS, "Error: bgp_parse_capabilities: unknown AFI: %d", afi); + goto err; + } + + break; +#endif + case 69: /* ADD-PATH capability, draft */ if (cl % 4) goto err; @@ -1047,7 +1189,10 @@ bgp_rx_end_mark(struct bgp_proto *p) } -#define DECODE_PREFIX(pp, ll) do { \ +/* DECODE_PREFIX definition moved to bgp.h, so that it can also be + * used by attrs.c : decode_bgpsec_attr() */ +/* +#define DECODE_PREFIX(pp, ll) do { \ if (p->add_path_rx) \ { \ if (ll < 5) { err=1; goto done; } \ @@ -1068,6 +1213,7 @@ bgp_rx_end_mark(struct bgp_proto *p) prefix = ipa_and(prefix, ipa_mkmask(b)); \ pxlen = b; \ } while (0) +*/ static inline void @@ -1169,7 +1315,8 @@ bgp_set_next_hop(struct bgp_proto *p, rta *a) return 1; } -#ifndef IPV6 /* IPv4 version */ + +#if !defined(IPV6) && !defined(CONFIG_BGPSEC) /* IPv4 version */ static void bgp_do_rx_update(struct bgp_conn *conn, @@ -1204,7 +1351,8 @@ bgp_do_rx_update(struct bgp_conn *conn, if (!attr_len && !nlri_len) /* shortcut */ return; - a0 = bgp_decode_attrs(conn, attrs, attr_len, bgp_linpool, nlri_len); + /* Note: bgp_linpool, nlri, nlri_len needed for bgpsec decoding */ + a0 = bgp_decode_attrs(conn, attrs, attr_len, bgp_linpool, nlri, nlri_len); if (conn->state != BS_ESTABLISHED) /* fatal error during decoding */ return; @@ -1236,8 +1384,12 @@ bgp_do_rx_update(struct bgp_conn *conn, return; } -#else /* IPv6 version */ +#else /* IPv6 || BGPSEC version */ + +/* DO_NLRI definition moved to bgp.h, so that it can also be + * used by attrs.c : decode_bgpsec_attr() */ +/* #define DO_NLRI(name) \ start = x = p->name##_start; \ len = len0 = p->name##_len; \ @@ -1253,14 +1405,16 @@ bgp_do_rx_update(struct bgp_conn *conn, else \ af = 0; \ if (af == BGP_AF_IPV6) +*/ static void bgp_attach_next_hop(rta *a0, byte *x) { ip_addr *nh = (ip_addr *) bgp_attach_attr_wa(&a0->eattrs, bgp_linpool, BA_NEXT_HOP, NEXT_HOP_LENGTH); - memcpy(nh, x+1, 16); + memcpy(nh, x+1, (*x <= sizeof(ip_addr) ? *x : sizeof(ip_addr)) ); ipa_ntoh(nh[0]); +#ifdef IPv6 /* We store received link local address in the other part of BA_NEXT_HOP eattr. */ if (*x == 32) { @@ -1269,6 +1423,7 @@ bgp_attach_next_hop(rta *a0, byte *x) } else nh[1] = IPA_NONE; +#endif } @@ -1291,7 +1446,7 @@ bgp_do_rx_update(struct bgp_conn *conn, p->mp_reach_len = 0; p->mp_unreach_len = 0; - a0 = bgp_decode_attrs(conn, attrs, attr_len, bgp_linpool, 0); + a0 = bgp_decode_attrs(conn, attrs, attr_len, bgp_linpool, nlri, nlri_len); if (conn->state != BS_ESTABLISHED) /* fatal error during decoding */ return; @@ -1316,9 +1471,9 @@ bgp_do_rx_update(struct bgp_conn *conn, DO_NLRI(mp_reach) { - /* Create fake NEXT_HOP attribute */ - if (len < 1 || (*x != 16 && *x != 32) || len < *x + 2) - { err = 9; goto done; } + /* Create fake NEXT_HOP attribute, check IP addr length */ + if (len < 1 || (*x != 4 && *x != 16 && *x != 32) || len < *x + 2) + { err = BGP_UPD_ERROR_OPT_ATTR; goto done; } if (a0) bgp_attach_next_hop(a0, x); @@ -1350,7 +1505,7 @@ bgp_do_rx_update(struct bgp_conn *conn, rta_free(a); if (err) /* Use subcode 9, not err */ - bgp_error(conn, 3, 9, NULL, 0); + bgp_error(conn, 3, BGP_UPD_ERROR_OPT_ATTR, NULL, 0); return; } @@ -1432,6 +1587,7 @@ static struct { { 3, 9, "Optional attribute error" }, { 3, 10, "Invalid network field" }, { 3, 11, "Malformed AS_PATH" }, + { 3, 12, "Bad BGPSEC Signature"}, { 4, 0, "Hold timer expired" }, { 5, 0, "Finite state machine error" }, /* Subcodes are according to [RFC6608] */ { 5, 1, "Unexpected message in OpenSent state" }, diff --git a/sysdep/autoconf.h.in b/sysdep/autoconf.h.in index a9e46e27..079f903c 100644 --- a/sysdep/autoconf.h.in +++ b/sysdep/autoconf.h.in @@ -43,6 +43,7 @@ #undef CONFIG_BGP #undef CONFIG_OSPF #undef CONFIG_PIPE +#undef CONFIG_BGPSEC /* We use multithreading */ #undef USE_PTHREADS