0
0
mirror of https://gitlab.nic.cz/labs/bird.git synced 2024-10-18 09:58:43 +00:00

The Michael Baer's patch for BGPsec Support

Imported from:
  https://securerouting.net/download/bird-1.5.0-bgpsec-0.7.tar.bz2
This commit is contained in:
Michael Baer 2016-05-26 14:09:08 +02:00 committed by Pavel Tvrdik
parent deec752ef9
commit e728484249
19 changed files with 3093 additions and 79 deletions

275
README.bgpsec Normal file
View File

@ -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.

View File

@ -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

View File

@ -19,6 +19,9 @@
#include "lib/string.h"
/* XXX need ifdef to intsead use bsd's #include <sys/endian.h> */
#include <endian.h>
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

View File

@ -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

View File

@ -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)

File diff suppressed because it is too large Load Diff

View File

@ -4,6 +4,14 @@
* (c) 2000 Martin Mares <mj@ucw.cz>
*
* 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 <stdio.h>
#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

View File

@ -4,6 +4,14 @@
* (c) 2000 Martin Mares <mj@ucw.cz>
*
* 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_DPA 0x0b /* DPA deprecated */
#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_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 /* [RFC4893] */
#define BA_AS4_AGGREGATOR 0x12
#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

11
proto/bgp/bgpsec/Makefile Normal file
View File

@ -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)

View File

@ -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(<SKI>) {
if (/X509v3 Subject Key Identifier/) {
$ski = <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

27
proto/bgp/bgpsec/config.Y Normal file
View File

@ -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

225
proto/bgp/bgpsec/keytool.py Executable file
View File

@ -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())

View File

@ -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)

287
proto/bgp/bgpsec/tests.c Normal file
View File

@ -0,0 +1,287 @@
#include "validate.h"
#include <sys/param.h>
#include <openssl/ec.h>
#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;
}

253
proto/bgp/bgpsec/validate.c Normal file
View File

@ -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 <stdio.h>
#include <sys/param.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <openssl/ec.h>
#include <openssl/x509.h>
#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;
}

159
proto/bgp/bgpsec/validate.h Normal file
View File

@ -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 <stdint.h>
#include <openssl/evp.h>
#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

View File

@ -4,6 +4,14 @@
* (c) 2000 Martin Mares <mj@ucw.cz>
*
* 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); }
;

View File

@ -4,7 +4,14 @@
* (c) 2000 Martin Mares <mj@ucw.cz>
*
* 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,7 +499,11 @@ 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);
@ -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;
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,6 +1189,9 @@ bgp_rx_end_mark(struct bgp_proto *p)
}
/* 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) \
{ \
@ -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" },

View File

@ -43,6 +43,7 @@
#undef CONFIG_BGP
#undef CONFIG_OSPF
#undef CONFIG_PIPE
#undef CONFIG_BGPSEC
/* We use multithreading */
#undef USE_PTHREADS