From f5011cd5ddfd0eabe359d7013747823c6bd4ed3f Mon Sep 17 00:00:00 2001 From: Mahdi Varasteh Date: Tue, 12 Sep 2023 15:09:44 +0330 Subject: [ospfd]: add support for RFC 5709 HMAC-SHA Auth This patch includes: * Implementation of RFC 5709 support in OSPF. Using openssl library and FRR key-chain, one can use SHA1, SHA256, SHA384, SHA512 and keyed-MD5( backward compatibility with RFC 2328) HMAC algs. * Updating documentation of OSPF * add topotests for new HMAC algorithms Signed-off-by: Mahdi Varasteh --- doc/user/ospfd.rst | 32 + ospfd/ospf_auth.c | 705 +++++++++++++++++++++ ospfd/ospf_auth.h | 20 + ospfd/ospf_errors.c | 6 +- ospfd/ospf_errors.h | 2 +- ospfd/ospf_interface.c | 3 + ospfd/ospf_interface.h | 7 + ospfd/ospf_main.c | 2 + ospfd/ospf_packet.c | 363 +---------- ospfd/ospf_vty.c | 142 ++++- ospfd/subdir.am | 2 + python/xref2vtysh.py | 2 +- tests/topotests/lib/ospf.py | 4 + .../test_ospf_authentication.py | 557 ++++++++++++++-- vtysh/vtysh.h | 2 +- 15 files changed, 1419 insertions(+), 430 deletions(-) create mode 100644 ospfd/ospf_auth.c create mode 100644 ospfd/ospf_auth.h diff --git a/doc/user/ospfd.rst b/doc/user/ospfd.rst index b61c9448d..26b2b4397 100644 --- a/doc/user/ospfd.rst +++ b/doc/user/ospfd.rst @@ -599,6 +599,38 @@ Interfaces KEY is the actual message digest key, of up to 16 chars (larger strings will be truncated), and is associated with the given KEYID. +.. clicmd:: ip ospf authentication key-chain KEYCHAIN + + Specify that HMAC cryptographic authentication must be used on this interface + using a key chain. Overrides any authentication enabled on a per-area basis + (:clicmd:`area A.B.C.D authentication message-digest`) + + * ``KEYCHAIN``: Specifies the name of the key chain that contains the authentication + key(s) and cryptographic algorithms to be used for OSPF authentication. The key chain + is a logical container that holds one or more authentication keys, + allowing for key rotation and management. + + Note that OSPF HMAC cryptographic authentication requires that time never go backwards + (correct time is NOT important, only that it never goes backwards), even + across resets, if ospfd is to be able to promptly reestablish adjacencies + with its neighbours after restarts/reboots. The host should have system time + be set at boot from an external or non-volatile source (e.g. battery backed + clock, NTP, etc.) or else the system clock should be periodically saved to + non-volatile storage and restored at boot if HMAC cryptographic authentication is to be + expected to work reliably. + + Example: + + .. code:: frr + + r1(config)#key chain temp + r1(config-keychain)#key 13 + r1(config-keychain-key)#key-string ospf + r1(config-keychain-key)#cryptographic-algorithm hmac-sha-256 + r1(config)#int eth0 + r1(config-if)#ip ospf authentication key-chain temp + r1(config-if)#ip ospf area 0 + .. clicmd:: ip ospf cost (1-65535) diff --git a/ospfd/ospf_auth.c b/ospfd/ospf_auth.c new file mode 100644 index 000000000..2d13d4e9a --- /dev/null +++ b/ospfd/ospf_auth.c @@ -0,0 +1,705 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (C) 2023 Amnesh Inc. + * Mahdi Varasteh + */ + +#include + +#include "linklist.h" +#include "if.h" +#include "checksum.h" + +#include "ospfd/ospfd.h" +#include "ospfd/ospf_errors.h" +#include "ospfd/ospf_interface.h" +#include "ospfd/ospf_asbr.h" +#include "ospfd/ospf_lsa.h" +#include "ospfd/ospf_lsdb.h" +#include "ospfd/ospf_neighbor.h" +#include "ospfd/ospf_auth.h" +#include "ospfd/ospf_nsm.h" +#include "ospfd/ospf_dump.h" +#include "ospfd/ospf_packet.h" +#include "ospfd/ospf_network.h" +#include "ospfd/ospf_flood.h" +#include "ospfd/ospf_dump.h" +#include "ospfd/ospf_bfd.h" +#include "ospfd/ospf_gr.h" +#ifdef CRYPTO_INTERNAL +#include "sha256.h" +#include "md5.h" +#endif + +const uint8_t ospf_auth_apad[KEYCHAIN_MAX_HASH_SIZE] = { + 0x87, 0x8F, 0xE1, 0xF3, 0x87, 0x8F, 0xE1, 0xF3, 0x87, 0x8F, 0xE1, + 0xF3, 0x87, 0x8F, 0xE1, 0xF3, 0x87, 0x8F, 0xE1, 0xF3, 0x87, 0x8F, + 0xE1, 0xF3, 0x87, 0x8F, 0xE1, 0xF3, 0x87, 0x8F, 0xE1, 0xF3, 0x87, + 0x8F, 0xE1, 0xF3, 0x87, 0x8F, 0xE1, 0xF3, 0x87, 0x8F, 0xE1, 0xF3, + 0x87, 0x8F, 0xE1, 0xF3, 0x87, 0x8F, 0xE1, 0xF3, 0x87, 0x8F, 0xE1, + 0xF3, 0x87, 0x8F, 0xE1, 0xF3, 0x87, 0x8F, 0xE1, 0xF3 +}; + +static int ospf_check_sum(struct ospf_header *ospfh) +{ + uint32_t ret; + uint16_t sum; + + /* clear auth_data for checksum. */ + memset(ospfh->u.auth_data, 0, OSPF_AUTH_SIMPLE_SIZE); + + /* keep checksum and clear. */ + sum = ospfh->checksum; + memset(&ospfh->checksum, 0, sizeof(uint16_t)); + + /* calculate checksum. */ + ret = in_cksum(ospfh, ntohs(ospfh->length)); + + if (ret != sum) { + zlog_info("%s: checksum mismatch, my %X, his %X", __func__, ret, + sum); + return 0; + } + + return 1; +} + +#ifdef CRYPTO_OPENSSL +static const EVP_MD *ospf_auth_get_openssl_evp_md_from_key(struct key *key) +{ + if (key->hash_algo == KEYCHAIN_ALGO_HMAC_SHA1) + return EVP_get_digestbyname("sha1"); + else if (key->hash_algo == KEYCHAIN_ALGO_HMAC_SHA256) + return EVP_get_digestbyname("sha256"); + else if (key->hash_algo == KEYCHAIN_ALGO_HMAC_SHA384) + return EVP_get_digestbyname("sha384"); + else if (key->hash_algo == KEYCHAIN_ALGO_HMAC_SHA512) + return EVP_get_digestbyname("sha512"); + return NULL; +} +#endif + +static int ospf_auth_check_hmac_sha_digest(struct ospf_interface *oi, + struct ospf_header *ospfh, + struct ip *iph, + struct key *key) +{ + unsigned char digest[KEYCHAIN_MAX_HASH_SIZE]; + struct ospf_neighbor *nbr; + uint16_t length = ntohs(ospfh->length); + uint16_t hash_length = keychain_get_hash_len(key->hash_algo); +#ifdef CRYPTO_OPENSSL + unsigned int openssl_hash_length = hash_length; + HMAC_CTX *ctx; + const EVP_MD *md_alg = ospf_auth_get_openssl_evp_md_from_key(key); + + if (!md_alg) { + flog_warn(EC_OSPF_AUTH, + "interface %s: invalid HMAC algorithm, Router-ID: %pI4", + IF_NAME(oi), &ospfh->router_id); + return 0; + } +#elif CRYPTO_INTERNAL + HMAC_SHA256_CTX ctx; + + if (key->hash_algo != KEYCHAIN_ALGO_HMAC_SHA256) { + flog_warn(EC_OSPF_AUTH, + "interface %s: HMAC algorithm not supported, Router-ID: %pI4", + IF_NAME(oi), &ospfh->router_id); + return 0; + } +#endif + /* check crypto seqnum. */ + nbr = ospf_nbr_lookup(oi, iph, ospfh); + + if (nbr && + ntohl(nbr->crypt_seqnum) > ntohl(ospfh->u.crypt.crypt_seqnum)) { + flog_warn(EC_OSPF_AUTH, + "interface %s: ospf_check_hmac_sha bad sequence %u (expect %d), Router-ID: %pI4", + IF_NAME(oi), ntohl(ospfh->u.crypt.crypt_seqnum), + ntohl(nbr->crypt_seqnum), &ospfh->router_id); + return 0; + } +#ifdef CRYPTO_OPENSSL + ctx = HMAC_CTX_new(); + HMAC_Init_ex(ctx, key->string, strlen(key->string), md_alg, NULL); + HMAC_Update(ctx, (const unsigned char *)ospfh, length); + HMAC_Update(ctx, (const unsigned char *)ospf_auth_apad, hash_length); + HMAC_Final(ctx, digest, &openssl_hash_length); + HMAC_CTX_free(ctx); +#elif CRYPTO_INTERNAL + memset(&ctx, 0, sizeof(ctx)); + HMAC__SHA256_Init(&ctx, key->string, strlen(key->string)); + HMAC__SHA256_Update(&ctx, ospfh, length); + HMAC__SHA256_Update(&ctx, ospf_auth_apad, hash_length); + HMAC__SHA256_Final(digest, &ctx); +#endif + if (memcmp((caddr_t)ospfh + length, digest, hash_length)) { + flog_warn(EC_OSPF_AUTH, + "interface %s: ospf_check_hmac_sha checksum mismatch %u, Router-ID: %pI4", + IF_NAME(oi), length, &ospfh->router_id); + return 0; + } + if (nbr) + nbr->crypt_seqnum = ospfh->u.crypt.crypt_seqnum; + return 1; +} + +static int ospf_auth_check_md5_digest(struct ospf_interface *oi, + struct ospf_header *ospfh, struct ip *iph, struct key *key) +{ +#ifdef CRYPTO_OPENSSL + EVP_MD_CTX *ctx; +#elif CRYPTO_INTERNAL + MD5_CTX ctx; +#endif + char auth_key[OSPF_AUTH_MD5_SIZE + 1]; + unsigned char digest[OSPF_AUTH_MD5_SIZE]; + struct ospf_neighbor *nbr; + struct crypt_key *ck = NULL; + uint16_t length = ntohs(ospfh->length); + + if (key == NULL) { + ck = ospf_crypt_key_lookup(OSPF_IF_PARAM(oi, auth_crypt), + ospfh->u.crypt.key_id); + if (ck == NULL) { + flog_warn( + EC_OSPF_AUTH, + "interface %s: %s no key %d, Router-ID: %pI4", + IF_NAME(oi), __func__, ospfh->u.crypt.key_id, &ospfh->router_id); + return 0; + } + } + /* check crypto seqnum. */ + nbr = ospf_nbr_lookup(oi, iph, ospfh); + + if (nbr && + ntohl(nbr->crypt_seqnum) > ntohl(ospfh->u.crypt.crypt_seqnum)) { + flog_warn(EC_OSPF_AUTH, + "interface %s: %s bad sequence %d (expect %d), Router-ID: %pI4", + IF_NAME(oi), __func__, ntohl(ospfh->u.crypt.crypt_seqnum), + ntohl(nbr->crypt_seqnum), &ospfh->router_id); + return 0; + } + + memset(auth_key, 0, OSPF_AUTH_MD5_SIZE + 1); + if (ck == NULL) + strlcpy(auth_key, key->string, OSPF_AUTH_MD5_SIZE + 1); + else + strlcpy(auth_key, (char *)ck->auth_key, OSPF_AUTH_MD5_SIZE + 1); + /* Generate a digest for the ospf packet - their digest + our digest. */ +#ifdef CRYPTO_OPENSSL + unsigned int md5_size = OSPF_AUTH_MD5_SIZE; + + ctx = EVP_MD_CTX_new(); + EVP_DigestInit(ctx, EVP_md5()); + EVP_DigestUpdate(ctx, ospfh, length); + EVP_DigestUpdate(ctx, auth_key, OSPF_AUTH_MD5_SIZE); + EVP_DigestFinal(ctx, digest, &md5_size); + EVP_MD_CTX_free(ctx); +#elif CRYPTO_INTERNAL + memset(&ctx, 0, sizeof(ctx)); + MD5Init(&ctx); + MD5Update(&ctx, ospfh, length); + MD5Update(&ctx, auth_key, OSPF_AUTH_MD5_SIZE); + MD5Final(digest, &ctx); +#endif + + /* compare the two */ + if (memcmp((caddr_t)ospfh + length, digest, OSPF_AUTH_MD5_SIZE)) { + flog_warn(EC_OSPF_AUTH, + "interface %s: ospf_check_md5 checksum mismatch, Router-ID: %pI4", + IF_NAME(oi), &ospfh->router_id); + return 0; + } + + /* save neighbor's crypt_seqnum */ + if (nbr) + nbr->crypt_seqnum = ospfh->u.crypt.crypt_seqnum; + return 1; +} + +static int ospf_auth_make_md5_digest(struct ospf_interface *oi, + struct ospf_packet *op, struct key *key) +{ + void *ibuf; + struct ospf_header *ospfh; + unsigned char digest[OSPF_AUTH_MD5_SIZE]; + uint16_t length; +#ifdef CRYPTO_OPENSSL + EVP_MD_CTX *ctx; +#elif CRYPTO_INTERNAL + MD5_CTX ctx; +#endif + char auth_key[OSPF_AUTH_MD5_SIZE + 1]; + + memset(auth_key, 0, OSPF_AUTH_MD5_SIZE + 1); + strlcpy(auth_key, key->string, OSPF_AUTH_MD5_SIZE + 1); + ibuf = STREAM_DATA(op->s); + ospfh = (struct ospf_header *)ibuf; + length = ntohs(ospfh->length); + /* Generate a digest for the ospf packet - their digest + our digest. */ +#ifdef CRYPTO_OPENSSL + unsigned int md5_size = OSPF_AUTH_MD5_SIZE; + + ctx = EVP_MD_CTX_new(); + EVP_DigestInit(ctx, EVP_md5()); + EVP_DigestUpdate(ctx, ospfh, length); + EVP_DigestUpdate(ctx, auth_key, OSPF_AUTH_MD5_SIZE); + EVP_DigestFinal(ctx, digest, &md5_size); + EVP_MD_CTX_free(ctx); +#elif CRYPTO_INTERNAL + memset(&ctx, 0, sizeof(ctx)); + MD5Init(&ctx); + MD5Update(&ctx, ospfh, length); + MD5Update(&ctx, auth_key, OSPF_AUTH_MD5_SIZE); + MD5Final(digest, &ctx); +#endif + + stream_put(op->s, digest, OSPF_AUTH_MD5_SIZE); + + op->length = ntohs(ospfh->length) + OSPF_AUTH_MD5_SIZE; + + if (stream_get_endp(op->s) != op->length) + /* XXX size_t */ + flog_warn(EC_OSPF_AUTH, + "%s: length mismatch stream %lu ospf_packet %u, Router-ID %pI4", + __func__, (unsigned long)stream_get_endp(op->s), + op->length, &ospfh->router_id); + + return OSPF_AUTH_MD5_SIZE; +} + +static int ospf_auth_make_hmac_sha_digest(struct ospf_interface *oi, + struct ospf_packet *op, + struct key *key) +{ + void *ibuf; + struct ospf_header *ospfh; + unsigned char digest[KEYCHAIN_MAX_HASH_SIZE] = { 0 }; + uint16_t hash_length = keychain_get_hash_len(key->hash_algo); + + ibuf = STREAM_DATA(op->s); + ospfh = (struct ospf_header *)ibuf; +#ifdef CRYPTO_OPENSSL + unsigned int openssl_hash_length = hash_length; + HMAC_CTX *ctx; + const EVP_MD *md_alg = ospf_auth_get_openssl_evp_md_from_key(key); + + if (!md_alg) { + flog_warn(EC_OSPF_AUTH, + "interface %s: invalid HMAC algorithm, Router-ID: %pI4", + IF_NAME(oi), &ospfh->router_id); + return 0; + } +#elif CRYPTO_INTERNAL + HMAC_SHA256_CTX ctx; + + if (key->hash_algo != KEYCHAIN_ALGO_HMAC_SHA256) { + flog_warn(EC_OSPF_AUTH, + "interface %s: HMAC algorithm not supported, Router-ID: %pI4", + IF_NAME(oi), &ospfh->router_id); + return 0; + } +#endif +#ifdef CRYPTO_OPENSSL + ctx = HMAC_CTX_new(); + HMAC_Init_ex(ctx, key->string, strlen(key->string), md_alg, NULL); + HMAC_Update(ctx, (const unsigned char *)ospfh, ntohs(ospfh->length)); + HMAC_Update(ctx, (const unsigned char *)ospf_auth_apad, hash_length); + HMAC_Final(ctx, digest, &openssl_hash_length); + HMAC_CTX_free(ctx); +#elif CRYPTO_INTERNAL + memset(&ctx, 0, sizeof(ctx)); + HMAC__SHA256_Init(&ctx, key->string, strlen(key->string)); + HMAC__SHA256_Update(&ctx, ospfh, ntohs(ospfh->length)); + HMAC__SHA256_Update(&ctx, ospf_auth_apad, hash_length); + HMAC__SHA256_Final(digest, &ctx); +#endif + stream_put(op->s, digest, hash_length); + + op->length = ntohs(ospfh->length) + hash_length; + + if (stream_get_endp(op->s) != op->length) + /* XXX size_t */ + flog_warn(EC_OSPF_AUTH, + "%s: length mismatch stream %lu ospf_packet %u, Router-ID %pI4", + __func__, (unsigned long)stream_get_endp(op->s), + op->length, &ospfh->router_id); + + return hash_length; +} + +int ospf_auth_check_digest(struct ospf_interface *oi, struct ip *iph, struct ospf_header *ospfh) +{ + struct keychain *keychain = NULL; + struct key *key = NULL; + int key_id = ospfh->u.crypt.key_id; + uint8_t auth_data_len = ospfh->u.crypt.auth_data_len; + + if (!OSPF_IF_PARAM(oi, keychain_name)) + return ospf_auth_check_md5_digest(oi, ospfh, iph, NULL); + + keychain = keychain_lookup(OSPF_IF_PARAM(oi, keychain_name)); + if (!keychain) { + if (IS_DEBUG_OSPF_PACKET(ospfh->type - 1, RECV)) + flog_warn(EC_OSPF_AUTH, + "interface %s: Keychain %s is not available, Router-ID %pI4", + IF_NAME(oi), OSPF_IF_PARAM(oi, keychain_name), + &ospfh->router_id); + return 0; + } + + key = key_lookup_for_accept(keychain, key_id); + if (!key) { + if (IS_DEBUG_OSPF_PACKET(ospfh->type - 1, RECV)) + flog_warn(EC_OSPF_AUTH, + "interface %s: Key ID %d not found in keychain %s, Router-ID %pI4", + IF_NAME(oi), key_id, keychain->name, + &ospfh->router_id); + return 0; + } + + if (key->string == NULL || key->hash_algo == KEYCHAIN_ALGO_NULL) { + if (IS_DEBUG_OSPF_PACKET(ospfh->type - 1, RECV)) + flog_warn(EC_OSPF_AUTH, + "interface %s: Key ID %d in keychain %s is incomplete, Router-ID %pI4", + IF_NAME(oi), key_id, keychain->name, + &ospfh->router_id); + return 0; + } + + if (keychain_get_hash_len(key->hash_algo) != auth_data_len) { + if (IS_DEBUG_OSPF_PACKET(ospfh->type - 1, RECV)) + flog_warn(EC_OSPF_AUTH, + "interface %s: Key ID %d in keychain %s hash length mismatch, Router-ID %pI4", + IF_NAME(oi), key_id, keychain->name, + &ospfh->router_id); + return 0; + } + + /* Backward compatibility with RFC 2328 keyed-MD5 authentication */ + if (key->hash_algo == KEYCHAIN_ALGO_MD5) + return ospf_auth_check_md5_digest(oi, ospfh, iph, key); + + return ospf_auth_check_hmac_sha_digest(oi, ospfh, iph, key); +} + +int ospf_auth_make_digest(struct ospf_interface *oi, struct ospf_packet *op) +{ + struct ospf_header *ospfh; + void *ibuf; + struct keychain *keychain = NULL; + struct key *key = NULL; + int key_id; + + ibuf = STREAM_DATA(op->s); + ospfh = (struct ospf_header *)ibuf; + + key_id = ospfh->u.crypt.key_id; + + if (!OSPF_IF_PARAM(oi, keychain_name)) { + if (IS_DEBUG_OSPF_PACKET(ospfh->type - 1, RECV)) + flog_warn(EC_OSPF_AUTH, + "interface %s: Keychain is not set, Router-ID %pI4", + IF_NAME(oi), &ospfh->router_id); + return 0; + } + + keychain = oi->keychain; + if (!keychain) { + if (IS_DEBUG_OSPF_PACKET(ospfh->type - 1, SEND)) + flog_warn(EC_OSPF_AUTH, + "interface %s: Keychain %s is not available to send, Router-ID %pI4", + IF_NAME(oi), OSPF_IF_PARAM(oi, keychain_name), + &ospfh->router_id); + return 0; + } + + key = oi->key; + if (!key) { + if (IS_DEBUG_OSPF_PACKET(ospfh->type - 1, SEND)) + flog_warn(EC_OSPF_AUTH, + "interface %s: Key ID %d not found in keychain %s, Router-ID %pI4", + IF_NAME(oi), key_id, keychain->name, + &ospfh->router_id); + return 0; + } + + if (key->string == NULL || key->hash_algo == KEYCHAIN_ALGO_NULL) { + if (IS_DEBUG_OSPF_PACKET(ospfh->type - 1, SEND)) + flog_warn(EC_OSPF_AUTH, + "interface %s: Key ID %d in keychain %s is incomplete, Router-ID %pI4", + IF_NAME(oi), key_id, keychain->name, + &ospfh->router_id); + return 0; + } + + /* Backward compatibility with RFC 2328 keyed-MD5 authentication */ + if (key->hash_algo == KEYCHAIN_ALGO_MD5) + return ospf_auth_make_md5_digest(oi, op, key); + else + return ospf_auth_make_hmac_sha_digest(oi, op, key); +} + +/* This function is called from ospf_write(), it will detect the + * authentication scheme and if it is MD5, it will change the sequence + * and update the MD5 digest. + */ + +int ospf_auth_make(struct ospf_interface *oi, struct ospf_packet *op) +{ + struct ospf_header *ospfh; + unsigned char digest[OSPF_AUTH_MD5_SIZE] = {0}; +#ifdef CRYPTO_OPENSSL + EVP_MD_CTX *ctx; +#elif CRYPTO_INTERNAL + MD5_CTX ctx; +#endif + void *ibuf; + uint32_t t; + struct crypt_key *ck; + const uint8_t *auth_key = NULL; + + ibuf = STREAM_DATA(op->s); + ospfh = (struct ospf_header *)ibuf; + + if (ntohs(ospfh->auth_type) != OSPF_AUTH_CRYPTOGRAPHIC) + return 0; + + /* We do this here so when we dup a packet, we don't have to + * waste CPU rewriting other headers. + + Note that frr_time /deliberately/ is not used here. + */ + t = (time(NULL) & 0xFFFFFFFF); + if (t > oi->crypt_seqnum) + oi->crypt_seqnum = t; + else + oi->crypt_seqnum++; + + ospfh->u.crypt.crypt_seqnum = htonl(oi->crypt_seqnum); + + /* Get MD5 Authentication key from auth_key list. */ + if (list_isempty(OSPF_IF_PARAM(oi, auth_crypt)) && OSPF_IF_PARAM(oi, keychain_name) == NULL) + auth_key = (const uint8_t *)digest; + else if (!list_isempty(OSPF_IF_PARAM(oi, auth_crypt))) { + ck = listgetdata(listtail(OSPF_IF_PARAM(oi, auth_crypt))); + auth_key = ck->auth_key; + } + + if (auth_key) { + /* Generate a digest for the entire packet + our secret key. */ +#ifdef CRYPTO_OPENSSL + unsigned int md5_size = OSPF_AUTH_MD5_SIZE; + + ctx = EVP_MD_CTX_new(); + EVP_DigestInit(ctx, EVP_md5()); + EVP_DigestUpdate(ctx, ibuf, ntohs(ospfh->length)); + EVP_DigestUpdate(ctx, auth_key, OSPF_AUTH_MD5_SIZE); + EVP_DigestFinal(ctx, digest, &md5_size); + EVP_MD_CTX_free(ctx); +#elif CRYPTO_INTERNAL + memset(&ctx, 0, sizeof(ctx)); + MD5Init(&ctx); + MD5Update(&ctx, ibuf, ntohs(ospfh->length)); + MD5Update(&ctx, auth_key, OSPF_AUTH_MD5_SIZE); + MD5Final(digest, &ctx); +#endif + + /* Append md5 digest to the end of the stream. */ + stream_put(op->s, digest, OSPF_AUTH_MD5_SIZE); + + /* We do *NOT* increment the OSPF header length. */ + op->length = ntohs(ospfh->length) + OSPF_AUTH_MD5_SIZE; + + if (stream_get_endp(op->s) != op->length) + /* XXX size_t */ + flog_warn( + EC_OSPF_AUTH, + "%s: length mismatch stream %lu ospf_packet %u, Router-ID %pI4", + __func__, (unsigned long)stream_get_endp(op->s), + op->length, &ospfh->router_id); + + return OSPF_AUTH_MD5_SIZE; + } else + return ospf_auth_make_digest(oi, op); +} + +/* Return 1, if the packet is properly authenticated and checksummed, + * 0 otherwise. In particular, check that AuType header field is valid and + * matches the locally configured AuType, and that D.5 requirements are met. + */ +int ospf_auth_check(struct ospf_interface *oi, struct ip *iph, + struct ospf_header *ospfh) +{ + uint16_t iface_auth_type; + uint16_t pkt_auth_type = ntohs(ospfh->auth_type); + + iface_auth_type = ospf_auth_type(oi); + + switch (pkt_auth_type) { + case OSPF_AUTH_NULL: /* RFC2328 D.5.1 */ + if (iface_auth_type != OSPF_AUTH_NULL) { + if (IS_DEBUG_OSPF_PACKET(ospfh->type - 1, RECV)) + flog_warn( + EC_OSPF_PACKET, + "interface %s: auth-type mismatch, local %s, rcvd Null, Router-ID %pI4", + IF_NAME(oi), + lookup_msg(ospf_auth_type_str, + iface_auth_type, NULL), + &ospfh->router_id); + return 0; + } + if (!ospf_check_sum(ospfh)) { + if (IS_DEBUG_OSPF_PACKET(ospfh->type - 1, RECV)) + flog_warn( + EC_OSPF_PACKET, + "interface %s: Null auth OK, but checksum error, Router-ID %pI4", + IF_NAME(oi), + &ospfh->router_id); + return 0; + } + return 1; + case OSPF_AUTH_SIMPLE: /* RFC2328 D.5.2 */ + if (iface_auth_type != OSPF_AUTH_SIMPLE) { + if (IS_DEBUG_OSPF_PACKET(ospfh->type - 1, RECV)) + flog_warn( + EC_OSPF_PACKET, + "interface %s: auth-type mismatch, local %s, rcvd Simple, Router-ID %pI4", + IF_NAME(oi), + lookup_msg(ospf_auth_type_str, + iface_auth_type, NULL), + &ospfh->router_id); + return 0; + } + if (memcmp(OSPF_IF_PARAM(oi, auth_simple), ospfh->u.auth_data, + OSPF_AUTH_SIMPLE_SIZE)) { + if (IS_DEBUG_OSPF_PACKET(ospfh->type - 1, RECV)) + flog_warn( + EC_OSPF_PACKET, + "interface %s: Simple auth failed, Router-ID %pI4", + IF_NAME(oi), &ospfh->router_id); + return 0; + } + if (!ospf_check_sum(ospfh)) { + if (IS_DEBUG_OSPF_PACKET(ospfh->type - 1, RECV)) + flog_warn( + EC_OSPF_PACKET, + "interface %s: Simple auth OK, checksum error, Router-ID %pI4", + IF_NAME(oi), + &ospfh->router_id); + return 0; + } + return 1; + case OSPF_AUTH_CRYPTOGRAPHIC: /* RFC2328 D.5.3 */ + if (iface_auth_type != OSPF_AUTH_CRYPTOGRAPHIC) { + if (IS_DEBUG_OSPF_PACKET(ospfh->type - 1, RECV)) + flog_warn( + EC_OSPF_PACKET, + "interface %s: auth-type mismatch, local %s, rcvd Cryptographic, Router-ID %pI4", + IF_NAME(oi), + lookup_msg(ospf_auth_type_str, + iface_auth_type, NULL), + &ospfh->router_id); + return 0; + } + if (ospfh->checksum) { + if (IS_DEBUG_OSPF_PACKET(ospfh->type - 1, RECV)) + flog_warn( + EC_OSPF_PACKET, + "interface %s: OSPF header checksum is not 0, Router-ID %pI4", + IF_NAME(oi), &ospfh->router_id); + return 0; + } + /* If `authentication message-digest` key is not set, we try keychain crypto */ + if (OSPF_IF_PARAM(oi, keychain_name) || !list_isempty(OSPF_IF_PARAM(oi, auth_crypt))) + return ospf_auth_check_digest(oi, iph, ospfh); + if (IS_DEBUG_OSPF_PACKET(ospfh->type - 1, RECV)) + flog_warn( + EC_OSPF_AUTH, + "interface %s: MD5 auth failed, Router-ID %pI4", + IF_NAME(oi), &ospfh->router_id); + return 0; + default: + if (IS_DEBUG_OSPF_PACKET(ospfh->type - 1, RECV)) + flog_warn( + EC_OSPF_PACKET, + "interface %s: invalid packet auth-type (%02x), Router-ID %pI4", + IF_NAME(oi), pkt_auth_type, &ospfh->router_id); + return 0; + } +} + +/* OSPF authentication checking function */ +int ospf_auth_type(struct ospf_interface *oi) +{ + int auth_type; + + if (OSPF_IF_PARAM(oi, auth_type) == OSPF_AUTH_NOTSET) + auth_type = oi->area->auth_type; + else + auth_type = OSPF_IF_PARAM(oi, auth_type); + + /* Handle case where MD5 key list, or a key-chain, is not configured aka Cisco */ + if (auth_type == OSPF_AUTH_CRYPTOGRAPHIC + && (list_isempty(OSPF_IF_PARAM(oi, auth_crypt)) + && OSPF_IF_PARAM(oi, keychain_name) == NULL)) + return OSPF_AUTH_NULL; + + return auth_type; +} + +/* Make Authentication Data. */ +int ospf_auth_make_data(struct ospf_interface *oi, struct ospf_header *ospfh) +{ + struct crypt_key *ck; + + switch (ospf_auth_type(oi)) { + case OSPF_AUTH_NULL: + /* memset (ospfh->u.auth_data, 0, sizeof(ospfh->u.auth_data)); + */ + break; + case OSPF_AUTH_SIMPLE: + memcpy(ospfh->u.auth_data, OSPF_IF_PARAM(oi, auth_simple), + OSPF_AUTH_SIMPLE_SIZE); + break; + case OSPF_AUTH_CRYPTOGRAPHIC: + if (OSPF_IF_PARAM(oi, keychain_name)) { + oi->keychain = keychain_lookup(OSPF_IF_PARAM(oi, keychain_name)); + if (oi->keychain) + oi->key = key_lookup_for_send(oi->keychain); + if (oi->key) { + ospfh->u.crypt.zero = 0; + ospfh->u.crypt.key_id = oi->key->index; + ospfh->u.crypt.auth_data_len = keychain_get_hash_len(oi->key->hash_algo); + } else { + /* If key is not set, then set 0. */ + ospfh->u.crypt.zero = 0; + ospfh->u.crypt.key_id = 0; + ospfh->u.crypt.auth_data_len = OSPF_AUTH_MD5_SIZE; + } + } else { + /* If key is not set, then set 0. */ + if (list_isempty(OSPF_IF_PARAM(oi, auth_crypt))) { + ospfh->u.crypt.zero = 0; + ospfh->u.crypt.key_id = 0; + ospfh->u.crypt.auth_data_len = OSPF_AUTH_MD5_SIZE; + } else { + ck = listgetdata( + listtail(OSPF_IF_PARAM(oi, auth_crypt))); + ospfh->u.crypt.zero = 0; + ospfh->u.crypt.key_id = ck->key_id; + ospfh->u.crypt.auth_data_len = OSPF_AUTH_MD5_SIZE; + } + } + /* note: the seq is done in ospf_auth_make() */ + break; + default: + /* memset (ospfh->u.auth_data, 0, sizeof(ospfh->u.auth_data)); + */ + break; + } + + return 0; +} diff --git a/ospfd/ospf_auth.h b/ospfd/ospf_auth.h new file mode 100644 index 000000000..6f6d3db58 --- /dev/null +++ b/ospfd/ospf_auth.h @@ -0,0 +1,20 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (C) 2023 Amnesh Inc. + * Mahdi Varasteh + */ + +#ifndef _ZEBRA_OSPF_AUTH_H +#define _ZEBRA_OSPF_AUTH_H + +#include +#include + +int ospf_auth_check(struct ospf_interface *oi, struct ip *iph, struct ospf_header *ospfh); +int ospf_auth_check_digest(struct ospf_interface *oi, struct ip *iph, struct ospf_header *ospfh); +int ospf_auth_make(struct ospf_interface *oi, struct ospf_packet *op); +int ospf_auth_make_digest(struct ospf_interface *oi, struct ospf_packet *op); +int ospf_auth_type(struct ospf_interface *oi); +int ospf_auth_make_data(struct ospf_interface *oi, struct ospf_header *ospfh); + +#endif /* _ZEBRA_OSPF_AUTH_H */ diff --git a/ospfd/ospf_errors.c b/ospfd/ospf_errors.c index 16aa3ab52..cf58c6ad8 100644 --- a/ospfd/ospf_errors.c +++ b/ospfd/ospf_errors.c @@ -19,9 +19,9 @@ static struct log_ref ferr_ospf_warn[] = { .suggestion = "Do not use this particular set command for an ospf route-map", }, { - .code = EC_OSPF_MD5, - .title = "OSPF has noticed a MD5 issue", - .description = "Something has gone wrong with the calculation of the MD5 data", + .code = EC_OSPF_AUTH, + .title = "OSPF has noticed an authentication issue", + .description = "Something has gone wrong with the calculation of the authentication data", .suggestion = "Ensure your key is correct, gather log data from this side as well as peer and open an Issue", }, { diff --git a/ospfd/ospf_errors.h b/ospfd/ospf_errors.h index dcf9a65ce..e63cb7409 100644 --- a/ospfd/ospf_errors.h +++ b/ospfd/ospf_errors.h @@ -22,7 +22,7 @@ enum ospf_log_refs { EC_OSPF_INVALID_ALGORITHM, EC_OSPF_FSM_INVALID_STATE, EC_OSPF_SET_METRIC_PLUS, - EC_OSPF_MD5, + EC_OSPF_AUTH, EC_OSPF_PACKET, EC_OSPF_LARGE_LSA, EC_OSPF_LSA_UNEXPECTED, diff --git a/ospfd/ospf_interface.c b/ospfd/ospf_interface.c index bdab672b4..760141932 100644 --- a/ospfd/ospf_interface.c +++ b/ospfd/ospf_interface.c @@ -549,6 +549,7 @@ static struct ospf_if_params *ospf_new_if_params(void) UNSET_IF_PARAM(oip, auth_type); UNSET_IF_PARAM(oip, if_area); UNSET_IF_PARAM(oip, opaque_capable); + UNSET_IF_PARAM(oip, keychain_name); oip->auth_crypt = list_new(); @@ -566,6 +567,7 @@ static void ospf_del_if_params(struct interface *ifp, struct ospf_if_params *oip) { list_delete(&oip->auth_crypt); + XFREE(MTYPE_OSPF_IF_PARAMS, oip->keychain_name); ospf_interface_disable_bfd(ifp, oip); ldp_sync_info_free(&(oip->ldp_sync_info)); XFREE(MTYPE_OSPF_IF_PARAMS, oip); @@ -601,6 +603,7 @@ void ospf_free_if_params(struct interface *ifp, struct in_addr addr) !OSPF_IF_PARAM_CONFIGURED(oip, if_area) && !OSPF_IF_PARAM_CONFIGURED(oip, opaque_capable) && !OSPF_IF_PARAM_CONFIGURED(oip, prefix_suppression) && + !OSPF_IF_PARAM_CONFIGURED(oip, keychain_name) && listcount(oip->auth_crypt) == 0) { ospf_del_if_params(ifp, oip); rn->info = NULL; diff --git a/ospfd/ospf_interface.h b/ospfd/ospf_interface.h index 47b70f803..e2290a881 100644 --- a/ospfd/ospf_interface.h +++ b/ospfd/ospf_interface.h @@ -10,6 +10,7 @@ #include "lib/bfd.h" #include "qobj.h" #include "hook.h" +#include "keychain.h" #include "ospfd/ospf_packet.h" #include "ospfd/ospf_spf.h" @@ -92,6 +93,8 @@ struct ospf_if_params { auth_crypt); /* List of Auth cryptographic data. */ DECLARE_IF_PARAM(int, auth_type); /* OSPF authentication type */ + DECLARE_IF_PARAM(char*, keychain_name); /* OSPF HMAC Cryptographic Authentication*/ + /* Other, non-configuration state */ uint32_t network_lsa_seqnum; /* Network LSA seqnum */ @@ -279,6 +282,10 @@ struct ospf_interface { uint32_t full_nbrs; + /* Buffered values for keychain and key */ + struct keychain *keychain; + struct key *key; + QOBJ_FIELDS; }; DECLARE_QOBJ_TYPE(ospf_interface); diff --git a/ospfd/ospf_main.c b/ospfd/ospf_main.c index 536bd592d..dd000241f 100644 --- a/ospfd/ospf_main.c +++ b/ospfd/ospf_main.c @@ -27,6 +27,7 @@ #include "vrf.h" #include "libfrr.h" #include "routemap.h" +#include "keychain.h" #include "ospfd/ospfd.h" #include "ospfd/ospf_interface.h" @@ -218,6 +219,7 @@ int main(int argc, char **argv) access_list_init(); prefix_list_init(); + keychain_init(); /* Configuration processing callback initialization. */ cmd_init_config_callbacks(ospf_config_start, ospf_config_end); diff --git a/ospfd/ospf_packet.c b/ospfd/ospf_packet.c index cfa0d5d57..b37efa3ef 100644 --- a/ospfd/ospf_packet.c +++ b/ospfd/ospf_packet.c @@ -41,6 +41,7 @@ #include "ospfd/ospf_errors.h" #include "ospfd/ospf_zebra.h" #include "ospfd/ospf_gr.h" +#include "ospfd/ospf_auth.h" /* * OSPF Fragmentation / fragmented writes @@ -99,27 +100,6 @@ static const uint16_t ospf_lsa_minlen[] = { OSPF_OPAQUE_LSA_MIN_SIZE, /* OSPF_OPAQUE_AS_LSA */ }; -/* for ospf_check_auth() */ -static int ospf_check_sum(struct ospf_header *); - -/* OSPF authentication checking function */ -static int ospf_auth_type(struct ospf_interface *oi) -{ - int auth_type; - - if (OSPF_IF_PARAM(oi, auth_type) == OSPF_AUTH_NOTSET) - auth_type = oi->area->auth_type; - else - auth_type = OSPF_IF_PARAM(oi, auth_type); - - /* Handle case where MD5 key list is not configured aka Cisco */ - if (auth_type == OSPF_AUTH_CRYPTOGRAPHIC - && list_isempty(OSPF_IF_PARAM(oi, auth_crypt))) - return OSPF_AUTH_NULL; - - return auth_type; -} - static struct ospf_packet *ospf_packet_new(size_t size) { struct ospf_packet *new; @@ -258,8 +238,8 @@ static struct ospf_packet *ospf_packet_dup(struct ospf_packet *op) "ospf_packet_dup stream %lu ospf_packet %u size mismatch", (unsigned long)STREAM_SIZE(op->s), op->length); - /* Reserve space for MD5 authentication that may be added later. */ - new = ospf_packet_new(stream_get_endp(op->s) + OSPF_AUTH_MD5_SIZE); + /* Reserve space for MD5/HMAC SHA authentication that may be added later. */ + new = ospf_packet_new(stream_get_endp(op->s) + KEYCHAIN_MAX_HASH_SIZE); stream_copy(new->s, op->s); new->dst = op->dst; @@ -274,7 +254,7 @@ static unsigned int ospf_packet_authspace(struct ospf_interface *oi) int auth = 0; if (ospf_auth_type(oi) == OSPF_AUTH_CRYPTOGRAPHIC) - auth = OSPF_AUTH_MD5_SIZE; + auth = KEYCHAIN_MAX_HASH_SIZE; return auth; } @@ -290,155 +270,6 @@ static unsigned int ospf_packet_max(struct ospf_interface *oi) return max; } - -static int ospf_check_md5_digest(struct ospf_interface *oi, - struct ospf_header *ospfh) -{ -#ifdef CRYPTO_OPENSSL - EVP_MD_CTX *ctx; -#elif CRYPTO_INTERNAL - MD5_CTX ctx; -#endif - unsigned char digest[OSPF_AUTH_MD5_SIZE]; - struct crypt_key *ck; - struct ospf_neighbor *nbr; - uint16_t length = ntohs(ospfh->length); - - /* Get secret key. */ - ck = ospf_crypt_key_lookup(OSPF_IF_PARAM(oi, auth_crypt), - ospfh->u.crypt.key_id); - if (ck == NULL) { - flog_warn( - EC_OSPF_MD5, - "interface %s: ospf_check_md5 no key %d, Router-ID: %pI4", - IF_NAME(oi), ospfh->u.crypt.key_id, &ospfh->router_id); - return 0; - } - - /* check crypto seqnum. */ - nbr = ospf_nbr_lookup_by_routerid(oi->nbrs, &ospfh->router_id); - - if (nbr - && ntohl(nbr->crypt_seqnum) > ntohl(ospfh->u.crypt.crypt_seqnum)) { - flog_warn( - EC_OSPF_MD5, - "interface %s: ospf_check_md5 bad sequence %d (expect %d), Router-ID: %pI4", - IF_NAME(oi), ntohl(ospfh->u.crypt.crypt_seqnum), - ntohl(nbr->crypt_seqnum), &ospfh->router_id); - return 0; - } - - /* Generate a digest for the ospf packet - their digest + our digest. */ -#ifdef CRYPTO_OPENSSL - unsigned int md5_size = OSPF_AUTH_MD5_SIZE; - ctx = EVP_MD_CTX_new(); - EVP_DigestInit(ctx, EVP_md5()); - EVP_DigestUpdate(ctx, ospfh, length); - EVP_DigestUpdate(ctx, ck->auth_key, OSPF_AUTH_MD5_SIZE); - EVP_DigestFinal(ctx, digest, &md5_size); - EVP_MD_CTX_free(ctx); -#elif CRYPTO_INTERNAL - memset(&ctx, 0, sizeof(ctx)); - MD5Init(&ctx); - MD5Update(&ctx, ospfh, length); - MD5Update(&ctx, ck->auth_key, OSPF_AUTH_MD5_SIZE); - MD5Final(digest, &ctx); -#endif - - /* compare the two */ - if (memcmp((caddr_t)ospfh + length, digest, OSPF_AUTH_MD5_SIZE)) { - flog_warn( - EC_OSPF_MD5, - "interface %s: ospf_check_md5 checksum mismatch, Router-ID: %pI4", - IF_NAME(oi), &ospfh->router_id); - return 0; - } - - /* save neighbor's crypt_seqnum */ - if (nbr) - nbr->crypt_seqnum = ospfh->u.crypt.crypt_seqnum; - return 1; -} - -/* This function is called from ospf_write(), it will detect the - authentication scheme and if it is MD5, it will change the sequence - and update the MD5 digest. */ -static int ospf_make_md5_digest(struct ospf_interface *oi, - struct ospf_packet *op) -{ - struct ospf_header *ospfh; - unsigned char digest[OSPF_AUTH_MD5_SIZE] = {0}; -#ifdef CRYPTO_OPENSSL - EVP_MD_CTX *ctx; -#elif CRYPTO_INTERNAL - MD5_CTX ctx; -#endif - void *ibuf; - uint32_t t; - struct crypt_key *ck; - const uint8_t *auth_key; - - ibuf = STREAM_DATA(op->s); - ospfh = (struct ospf_header *)ibuf; - - if (ntohs(ospfh->auth_type) != OSPF_AUTH_CRYPTOGRAPHIC) - return 0; - - /* We do this here so when we dup a packet, we don't have to - waste CPU rewriting other headers. - - Note that frr_time /deliberately/ is not used here */ - t = (time(NULL) & 0xFFFFFFFF); - if (t > oi->crypt_seqnum) - oi->crypt_seqnum = t; - else - oi->crypt_seqnum++; - - ospfh->u.crypt.crypt_seqnum = htonl(oi->crypt_seqnum); - - /* Get MD5 Authentication key from auth_key list. */ - if (list_isempty(OSPF_IF_PARAM(oi, auth_crypt))) - auth_key = (const uint8_t *)digest; - else { - ck = listgetdata(listtail(OSPF_IF_PARAM(oi, auth_crypt))); - auth_key = ck->auth_key; - } - - /* Generate a digest for the entire packet + our secret key. */ -#ifdef CRYPTO_OPENSSL - unsigned int md5_size = OSPF_AUTH_MD5_SIZE; - ctx = EVP_MD_CTX_new(); - EVP_DigestInit(ctx, EVP_md5()); - EVP_DigestUpdate(ctx, ibuf, ntohs(ospfh->length)); - EVP_DigestUpdate(ctx, auth_key, OSPF_AUTH_MD5_SIZE); - EVP_DigestFinal(ctx, digest, &md5_size); - EVP_MD_CTX_free(ctx); -#elif CRYPTO_INTERNAL - memset(&ctx, 0, sizeof(ctx)); - MD5Init(&ctx); - MD5Update(&ctx, ibuf, ntohs(ospfh->length)); - MD5Update(&ctx, auth_key, OSPF_AUTH_MD5_SIZE); - MD5Final(digest, &ctx); -#endif - - /* Append md5 digest to the end of the stream. */ - stream_put(op->s, digest, OSPF_AUTH_MD5_SIZE); - - /* We do *NOT* increment the OSPF header length. */ - op->length = ntohs(ospfh->length) + OSPF_AUTH_MD5_SIZE; - - if (stream_get_endp(op->s) != op->length) - /* XXX size_t */ - flog_warn( - EC_OSPF_MD5, - "%s: length mismatch stream %lu ospf_packet %u, Router-ID %pI4", - __func__, (unsigned long)stream_get_endp(op->s), - op->length, &ospfh->router_id); - - return OSPF_AUTH_MD5_SIZE; -} - - static void ospf_ls_req_timer(struct event *thread) { struct ospf_neighbor *nbr; @@ -677,7 +508,7 @@ static void ospf_write(struct event *thread) ospf_if_ipmulticast(fd, oi->address, oi->ifp->ifindex); /* Rewrite the md5 signature & update the seq */ - ospf_make_md5_digest(oi, op); + ospf_auth_make(oi, op); /* Retrieve OSPF packet type. */ stream_set_getp(op->s, 1); @@ -2476,142 +2307,6 @@ static int ospf_check_network_mask(struct ospf_interface *oi, return 0; } -/* Return 1, if the packet is properly authenticated and checksummed, - 0 otherwise. In particular, check that AuType header field is valid and - matches the locally configured AuType, and that D.5 requirements are met. */ -static int ospf_check_auth(struct ospf_interface *oi, struct ospf_header *ospfh) -{ - struct crypt_key *ck; - uint16_t iface_auth_type; - uint16_t pkt_auth_type = ntohs(ospfh->auth_type); - - switch (pkt_auth_type) { - case OSPF_AUTH_NULL: /* RFC2328 D.5.1 */ - if (OSPF_AUTH_NULL != (iface_auth_type = ospf_auth_type(oi))) { - if (IS_DEBUG_OSPF_PACKET(ospfh->type - 1, RECV)) - flog_warn( - EC_OSPF_PACKET, - "interface %s: auth-type mismatch, local %s, rcvd Null, Router-ID %pI4", - IF_NAME(oi), - lookup_msg(ospf_auth_type_str, - iface_auth_type, NULL), - &ospfh->router_id); - return 0; - } - if (!ospf_check_sum(ospfh)) { - if (IS_DEBUG_OSPF_PACKET(ospfh->type - 1, RECV)) - flog_warn( - EC_OSPF_PACKET, - "interface %s: Null auth OK, but checksum error, Router-ID %pI4", - IF_NAME(oi), - &ospfh->router_id); - return 0; - } - return 1; - case OSPF_AUTH_SIMPLE: /* RFC2328 D.5.2 */ - if (OSPF_AUTH_SIMPLE - != (iface_auth_type = ospf_auth_type(oi))) { - if (IS_DEBUG_OSPF_PACKET(ospfh->type - 1, RECV)) - flog_warn( - EC_OSPF_PACKET, - "interface %s: auth-type mismatch, local %s, rcvd Simple, Router-ID %pI4", - IF_NAME(oi), - lookup_msg(ospf_auth_type_str, - iface_auth_type, NULL), - &ospfh->router_id); - return 0; - } - if (memcmp(OSPF_IF_PARAM(oi, auth_simple), ospfh->u.auth_data, - OSPF_AUTH_SIMPLE_SIZE)) { - if (IS_DEBUG_OSPF_PACKET(ospfh->type - 1, RECV)) - flog_warn( - EC_OSPF_PACKET, - "interface %s: Simple auth failed, Router-ID %pI4", - IF_NAME(oi), &ospfh->router_id); - return 0; - } - if (!ospf_check_sum(ospfh)) { - if (IS_DEBUG_OSPF_PACKET(ospfh->type - 1, RECV)) - flog_warn( - EC_OSPF_PACKET, - "interface %s: Simple auth OK, checksum error, Router-ID %pI4", - IF_NAME(oi), - &ospfh->router_id); - return 0; - } - return 1; - case OSPF_AUTH_CRYPTOGRAPHIC: /* RFC2328 D.5.3 */ - if (OSPF_AUTH_CRYPTOGRAPHIC - != (iface_auth_type = ospf_auth_type(oi))) { - if (IS_DEBUG_OSPF_PACKET(ospfh->type - 1, RECV)) - flog_warn( - EC_OSPF_PACKET, - "interface %s: auth-type mismatch, local %s, rcvd Cryptographic, Router-ID %pI4", - IF_NAME(oi), - lookup_msg(ospf_auth_type_str, - iface_auth_type, NULL), - &ospfh->router_id); - return 0; - } - if (ospfh->checksum) { - if (IS_DEBUG_OSPF_PACKET(ospfh->type - 1, RECV)) - flog_warn( - EC_OSPF_PACKET, - "interface %s: OSPF header checksum is not 0, Router-ID %pI4", - IF_NAME(oi), &ospfh->router_id); - return 0; - } - /* only MD5 crypto method can pass ospf_packet_examin() */ - if (NULL == (ck = listgetdata( - listtail(OSPF_IF_PARAM(oi, auth_crypt)))) - || ospfh->u.crypt.key_id != ck->key_id || - /* Condition above uses the last key ID on the list, - which is - different from what ospf_crypt_key_lookup() does. A - bug? */ - !ospf_check_md5_digest(oi, ospfh)) { - if (IS_DEBUG_OSPF_PACKET(ospfh->type - 1, RECV)) - flog_warn( - EC_OSPF_MD5, - "interface %s: MD5 auth failed, Router-ID %pI4", - IF_NAME(oi), &ospfh->router_id); - return 0; - } - return 1; - default: - if (IS_DEBUG_OSPF_PACKET(ospfh->type - 1, RECV)) - flog_warn( - EC_OSPF_PACKET, - "interface %s: invalid packet auth-type (%02x), Router-ID %pI4", - IF_NAME(oi), pkt_auth_type, &ospfh->router_id); - return 0; - } -} - -static int ospf_check_sum(struct ospf_header *ospfh) -{ - uint32_t ret; - uint16_t sum; - - /* clear auth_data for checksum. */ - memset(ospfh->u.auth_data, 0, OSPF_AUTH_SIMPLE_SIZE); - - /* keep checksum and clear. */ - sum = ospfh->checksum; - memset(&ospfh->checksum, 0, sizeof(uint16_t)); - - /* calculate checksum. */ - ret = in_cksum(ospfh, ntohs(ospfh->length)); - - if (ret != sum) { - zlog_info("%s: checksum mismatch, my %X, his %X", __func__, ret, - sum); - return 0; - } - - return 1; -} - /* Verify, that given link/TOS records are properly sized/aligned and match Router-LSA "# links" and "# TOS" fields as specified in RFC2328 A.4.2. */ static unsigned ospf_router_lsa_links_examin(struct router_lsa_link *link, @@ -2835,14 +2530,14 @@ static unsigned ospf_packet_examin(struct ospf_header *oh, if (ntohs(oh->auth_type) != OSPF_AUTH_CRYPTOGRAPHIC) bytesauth = 0; else { - if (oh->u.crypt.auth_data_len != OSPF_AUTH_MD5_SIZE) { + if (oh->u.crypt.auth_data_len > KEYCHAIN_MAX_HASH_SIZE) { if (IS_DEBUG_OSPF_PACKET(0, RECV)) zlog_debug( "%s: unsupported crypto auth length (%u B)", __func__, oh->u.crypt.auth_data_len); return MSG_NG; } - bytesauth = OSPF_AUTH_MD5_SIZE; + bytesauth = oh->u.crypt.auth_data_len; } if (bytesdeclared + bytesauth > bytesonwire) { if (IS_DEBUG_OSPF_PACKET(0, RECV)) @@ -2953,7 +2648,7 @@ static int ospf_verify_header(struct stream *ibuf, struct ospf_interface *oi, /* Check authentication. The function handles logging actions, where * required. */ - if (!ospf_check_auth(oi, ospfh)) + if (!ospf_auth_check(oi, iph, ospfh)) return -1; return 0; @@ -3261,44 +2956,6 @@ static void ospf_make_header(int type, struct ospf_interface *oi, stream_forward_endp(s, OSPF_HEADER_SIZE); } -/* Make Authentication Data. */ -static int ospf_make_auth(struct ospf_interface *oi, struct ospf_header *ospfh) -{ - struct crypt_key *ck; - - switch (ospf_auth_type(oi)) { - case OSPF_AUTH_NULL: - /* memset (ospfh->u.auth_data, 0, sizeof(ospfh->u.auth_data)); - */ - break; - case OSPF_AUTH_SIMPLE: - memcpy(ospfh->u.auth_data, OSPF_IF_PARAM(oi, auth_simple), - OSPF_AUTH_SIMPLE_SIZE); - break; - case OSPF_AUTH_CRYPTOGRAPHIC: - /* If key is not set, then set 0. */ - if (list_isempty(OSPF_IF_PARAM(oi, auth_crypt))) { - ospfh->u.crypt.zero = 0; - ospfh->u.crypt.key_id = 0; - ospfh->u.crypt.auth_data_len = OSPF_AUTH_MD5_SIZE; - } else { - ck = listgetdata( - listtail(OSPF_IF_PARAM(oi, auth_crypt))); - ospfh->u.crypt.zero = 0; - ospfh->u.crypt.key_id = ck->key_id; - ospfh->u.crypt.auth_data_len = OSPF_AUTH_MD5_SIZE; - } - /* note: the seq is done in ospf_make_md5_digest() */ - break; - default: - /* memset (ospfh->u.auth_data, 0, sizeof(ospfh->u.auth_data)); - */ - break; - } - - return 0; -} - /* Fill rest of OSPF header. */ static void ospf_fill_header(struct ospf_interface *oi, struct stream *s, uint16_t length) @@ -3317,7 +2974,9 @@ static void ospf_fill_header(struct ospf_interface *oi, struct stream *s, ospfh->checksum = 0; /* Add Authentication Data. */ - ospf_make_auth(oi, ospfh); + oi->keychain = NULL; + oi->key = NULL; + ospf_auth_make_data(oi, ospfh); } static int ospf_make_hello(struct ospf_interface *oi, struct stream *s) diff --git a/ospfd/ospf_vty.c b/ospfd/ospf_vty.c index 158e90df5..8c3ad7f37 100644 --- a/ospfd/ospf_vty.c +++ b/ospfd/ospf_vty.c @@ -21,6 +21,7 @@ #include #include "defaults.h" #include "lib/printfrr.h" +#include "keychain.h" #include "ospfd/ospfd.h" #include "ospfd/ospf_asbr.h" @@ -40,6 +41,7 @@ #include "ospfd/ospf_bfd.h" #include "ospfd/ospf_ldp_sync.h" #include "ospfd/ospf_network.h" +#include "ospfd/ospf_memory.h" FRR_CFG_DEFAULT_BOOL(OSPF_LOG_ADJACENCY_CHANGES, { .val_bool = true, .match_profile = "datacenter", }, @@ -808,6 +810,8 @@ struct ospf_vl_config_data { char *auth_key; /* simple password if present */ int crypto_key_id; /* Cryptographic key ID */ char *md5_key; /* MD5 authentication key */ + char *keychain; /* Cryptographic keychain */ + int del_keychain; int hello_interval; /* Obvious what these are... */ int retransmit_interval; int transmit_delay; @@ -890,6 +894,10 @@ static int ospf_vl_set_security(struct ospf_vl_data *vl_data, strlcpy((char *)IF_DEF_PARAMS(ifp)->auth_simple, vl_config->auth_key, sizeof(IF_DEF_PARAMS(ifp)->auth_simple)); + } else if (vl_config->keychain) { + SET_IF_PARAM(IF_DEF_PARAMS(ifp), keychain_name); + XFREE(MTYPE_OSPF_IF_PARAMS, IF_DEF_PARAMS(ifp)->keychain_name); + IF_DEF_PARAMS(ifp)->keychain_name = XSTRDUP(MTYPE_OSPF_IF_PARAMS, vl_config->keychain); } else if (vl_config->md5_key) { if (ospf_crypt_key_lookup(IF_DEF_PARAMS(ifp)->auth_crypt, vl_config->crypto_key_id) @@ -918,6 +926,9 @@ static int ospf_vl_set_security(struct ospf_vl_data *vl_data, ospf_crypt_key_delete(IF_DEF_PARAMS(ifp)->auth_crypt, vl_config->crypto_key_id); + } else if (vl_config->del_keychain) { + UNSET_IF_PARAM(IF_DEF_PARAMS(ifp), keychain_name); + XFREE(MTYPE_OSPF_IF_PARAMS, IF_DEF_PARAMS(ifp)->keychain_name); } return CMD_SUCCESS; @@ -1022,9 +1033,11 @@ static int ospf_vl_set(struct ospf *ospf, struct ospf_vl_config_data *vl_config) DEFUN (ospf_area_vlink, ospf_area_vlink_cmd, - "area virtual-link A.B.C.D [authentication []] []", + "area virtual-link A.B.C.D [authentication []] []", VLINK_HELPSTR_IPADDR "Enable authentication on this virtual link\n" + "Use a key-chain for cryptographic authentication keys\n" + "Key-chain name\n" "Use message-digest authentication\n" "Use null authentication\n" VLINK_HELPSTR_AUTH_MD5 @@ -1067,7 +1080,10 @@ DEFUN (ospf_area_vlink, vl_config.auth_type = OSPF_AUTH_SIMPLE; } - if (argv_find(argv, argc, "message-digest", &idx)) { + if (argv_find(argv, argc, "key-chain", &idx)) { + vl_config.auth_type = OSPF_AUTH_CRYPTOGRAPHIC; + vl_config.keychain = argv[idx+1]->arg; + } else if (argv_find(argv, argc, "message-digest", &idx)) { /* authentication message-digest */ vl_config.auth_type = OSPF_AUTH_CRYPTOGRAPHIC; } else if (argv_find(argv, argc, "null", &idx)) { @@ -1097,10 +1113,12 @@ DEFUN (ospf_area_vlink, DEFUN (no_ospf_area_vlink, no_ospf_area_vlink_cmd, - "no area virtual-link A.B.C.D [authentication []] []", + "no area virtual-link A.B.C.D [authentication []] []", NO_STR VLINK_HELPSTR_IPADDR "Enable authentication on this virtual link\n" + "Use a key-chain for cryptographic authentication keys\n" + "Key-chain name\n" "Use message-digest authentication\n" "Use null authentication\n" VLINK_HELPSTR_AUTH_MD5 @@ -1160,6 +1178,11 @@ DEFUN (no_ospf_area_vlink, vl_config.auth_type = OSPF_AUTH_NOTSET; } + if (argv_find(argv, argc, "key-chain", &idx)) { + vl_config.del_keychain = 1; + vl_config.keychain = NULL; + } + if (argv_find(argv, argc, "message-digest-key", &idx)) { vl_config.md5_key = NULL; vl_config.crypto_key_id = strtol(argv[idx + 1]->arg, NULL, 10); @@ -3597,18 +3620,41 @@ static void ospf_interface_auth_show(struct vty *vty, struct ospf_interface *oi, case OSPF_AUTH_CRYPTOGRAPHIC: { struct crypt_key *ckey; - if (list_isempty(OSPF_IF_PARAM(oi, auth_crypt))) - return; - - ckey = listgetdata(listtail(OSPF_IF_PARAM(oi, auth_crypt))); - if (ckey) { + if (OSPF_IF_PARAM(oi, keychain_name)) { if (use_json) { json_object_string_add(json, "authentication", - "authenticationMessageDigest"); + "authenticationKeyChain"); + json_object_string_add(json, "keychain", + OSPF_IF_PARAM(oi, keychain_name)); } else { vty_out(vty, " Cryptographic authentication enabled\n"); - vty_out(vty, " Algorithm:MD5\n"); + struct keychain *keychain = keychain_lookup(OSPF_IF_PARAM(oi, keychain_name)); + + if (keychain) { + struct key *key = key_lookup_for_send(keychain); + + if (key) { + vty_out(vty, " Sending SA: Key %u, Algorithm %s - key chain %s\n", + key->index, keychain_get_algo_name_by_id(key->hash_algo), + OSPF_IF_PARAM(oi, keychain_name)); + } + } + } + } else { + if (list_isempty(OSPF_IF_PARAM(oi, auth_crypt))) + return; + + ckey = listgetdata(listtail(OSPF_IF_PARAM(oi, auth_crypt))); + if (ckey) { + if (use_json) { + json_object_string_add(json, "authentication", + "authenticationMessageDigest"); + } else { + vty_out(vty, + " Cryptographic authentication enabled\n"); + vty_out(vty, " Algorithm:MD5\n"); + } } } break; @@ -7535,24 +7581,26 @@ DEFPY (show_ip_ospf_database, DEFUN (ip_ospf_authentication_args, ip_ospf_authentication_args_addr_cmd, - "ip ospf authentication [A.B.C.D]", + "ip ospf authentication [A.B.C.D]", "IP Information\n" "OSPF interface commands\n" "Enable authentication on this interface\n" "Use null authentication\n" "Use message-digest authentication\n" + "Use a key-chain for cryptographic authentication keys\n" + "Key-chain name\n" "Address of interface\n") { VTY_DECLVAR_CONTEXT(interface, ifp); int idx_encryption = 3; - int idx_ipv4 = 4; + int idx_ipv4 = argc-1; struct in_addr addr; int ret; struct ospf_if_params *params; params = IF_DEF_PARAMS(ifp); - if (argc == 5) { + if (argv[idx_ipv4]->type == IPV4_TKN) { ret = inet_aton(argv[idx_ipv4]->arg, &addr); if (!ret) { vty_out(vty, @@ -7575,6 +7623,17 @@ DEFUN (ip_ospf_authentication_args, if (argv[idx_encryption]->arg[0] == 'm') { SET_IF_PARAM(params, auth_type); params->auth_type = OSPF_AUTH_CRYPTOGRAPHIC; + UNSET_IF_PARAM(params, keychain_name); + XFREE(MTYPE_OSPF_IF_PARAMS, params->keychain_name); + return CMD_SUCCESS; + } + + if (argv[idx_encryption]->arg[0] == 'k') { + SET_IF_PARAM(params, auth_type); + params->auth_type = OSPF_AUTH_CRYPTOGRAPHIC; + SET_IF_PARAM(params, keychain_name); + params->keychain_name = XSTRDUP(MTYPE_OSPF_IF_PARAMS, argv[idx_encryption+1]->arg); + UNSET_IF_PARAM(params, auth_crypt); return CMD_SUCCESS; } @@ -7618,18 +7677,20 @@ DEFUN (ip_ospf_authentication, DEFUN (no_ip_ospf_authentication_args, no_ip_ospf_authentication_args_addr_cmd, - "no ip ospf authentication [A.B.C.D]", + "no ip ospf authentication [A.B.C.D]", NO_STR "IP Information\n" "OSPF interface commands\n" "Enable authentication on this interface\n" "Use null authentication\n" "Use message-digest authentication\n" + "Use a key-chain for cryptographic authentication keys\n" + "Key-chain name\n" "Address of interface\n") { VTY_DECLVAR_CONTEXT(interface, ifp); int idx_encryption = 4; - int idx_ipv4 = 5; + int idx_ipv4 = argc-1; struct in_addr addr; int ret; struct ospf_if_params *params; @@ -7638,7 +7699,7 @@ DEFUN (no_ip_ospf_authentication_args, params = IF_DEF_PARAMS(ifp); - if (argc == 6) { + if (argv[idx_ipv4]->type == IPV4_TKN) { ret = inet_aton(argv[idx_ipv4]->arg, &addr); if (!ret) { vty_out(vty, @@ -7653,6 +7714,10 @@ DEFUN (no_ip_ospf_authentication_args, } params->auth_type = OSPF_AUTH_NOTSET; UNSET_IF_PARAM(params, auth_type); + + XFREE(MTYPE_OSPF_IF_PARAMS, params->keychain_name); + UNSET_IF_PARAM(params, keychain_name); + if (params != IF_DEF_PARAMS(ifp)) { ospf_free_if_params(ifp, addr); ospf_if_update_params(ifp, addr); @@ -7660,7 +7725,8 @@ DEFUN (no_ip_ospf_authentication_args, } else { if (argv[idx_encryption]->arg[0] == 'n') { auth_type = OSPF_AUTH_NULL; - } else if (argv[idx_encryption]->arg[0] == 'm') { + } else if (argv[idx_encryption]->arg[0] == 'm' || + argv[idx_encryption]->arg[0] == 'k') { auth_type = OSPF_AUTH_CRYPTOGRAPHIC; } else { vty_out(vty, "Unexpected input encountered\n"); @@ -7676,6 +7742,8 @@ DEFUN (no_ip_ospf_authentication_args, if (params->auth_type == auth_type) { params->auth_type = OSPF_AUTH_NOTSET; UNSET_IF_PARAM(params, auth_type); + XFREE(MTYPE_OSPF_IF_PARAMS, params->keychain_name); + UNSET_IF_PARAM(params, keychain_name); } for (rn = route_top(IF_OIFS_PARAMS(ifp)); rn; @@ -7684,6 +7752,8 @@ DEFUN (no_ip_ospf_authentication_args, if (params->auth_type == auth_type) { params->auth_type = OSPF_AUTH_NOTSET; UNSET_IF_PARAM(params, auth_type); + XFREE(MTYPE_OSPF_IF_PARAMS, params->keychain_name); + UNSET_IF_PARAM(params, keychain_name); if (params != IF_DEF_PARAMS(ifp)) { ospf_free_if_params( ifp, rn->p.u.prefix4); @@ -12098,11 +12168,11 @@ static const char *const ospf_int_type_str[] = { "loopback" }; -static const char *interface_config_auth_str(struct ospf_if_params *params) +static int interface_config_auth_str(struct ospf_if_params *params, char *buf) { if (!OSPF_IF_PARAM_CONFIGURED(params, auth_type) || params->auth_type == OSPF_AUTH_NOTSET) - return NULL; + return 0; /* Translation tables are not that much help * here due to syntax @@ -12110,16 +12180,22 @@ static const char *interface_config_auth_str(struct ospf_if_params *params) switch (params->auth_type) { case OSPF_AUTH_NULL: - return " null"; + snprintf(buf, BUFSIZ, " null"); + break; case OSPF_AUTH_SIMPLE: - return ""; + snprintf(buf, BUFSIZ, " "); + break; case OSPF_AUTH_CRYPTOGRAPHIC: - return " message-digest"; + if (OSPF_IF_PARAM_CONFIGURED(params, keychain_name)) + snprintf(buf, BUFSIZ, " key-chain %s", params->keychain_name); + else + snprintf(buf, BUFSIZ, " message-digest"); + break; } - return ""; + return 1; } static int config_write_interface_one(struct vty *vty, struct vrf *vrf) @@ -12129,7 +12205,8 @@ static int config_write_interface_one(struct vty *vty, struct vrf *vrf) struct crypt_key *ck; struct route_node *rn = NULL; struct ospf_if_params *params; - const char *auth_str; + char buf[BUFSIZ]; + int ret = 0; int write = 0; FOR_ALL_INTERFACES (vrf, ifp) { @@ -12170,10 +12247,10 @@ static int config_write_interface_one(struct vty *vty, struct vrf *vrf) } /* OSPF interface authentication print */ - auth_str = interface_config_auth_str(params); - if (auth_str) { + ret = interface_config_auth_str(params, buf); + if (ret) { vty_out(vty, " ip ospf authentication%s", - auth_str); + buf); if (params != IF_DEF_PARAMS(ifp) && rn) vty_out(vty, " %pI4", &rn->p.u.prefix4); @@ -12613,8 +12690,9 @@ static int config_write_virtual_link(struct vty *vty, struct ospf *ospf) { struct listnode *node; struct ospf_vl_data *vl_data; - const char *auth_str; char buf[INET_ADDRSTRLEN]; + char buf2[BUFSIZ]; + int ret = 0; /* Virtual-Link print */ for (ALL_LIST_ELEMENTS_RO(ospf->vlinks, node, vl_data)) { @@ -12647,12 +12725,12 @@ static int config_write_virtual_link(struct vty *vty, struct ospf *ospf) vty_out(vty, " area %s virtual-link %pI4\n", buf, &vl_data->vl_peer); /* Auth type */ - auth_str = interface_config_auth_str( - IF_DEF_PARAMS(oi->ifp)); - if (auth_str) + ret = interface_config_auth_str( + IF_DEF_PARAMS(oi->ifp), buf2); + if (ret) vty_out(vty, " area %s virtual-link %pI4 authentication%s\n", - buf, &vl_data->vl_peer, auth_str); + buf, &vl_data->vl_peer, buf2); /* Auth key */ if (IF_DEF_PARAMS(vl_data->vl_oi->ifp)->auth_simple[0] != '\0') diff --git a/ospfd/subdir.am b/ospfd/subdir.am index 44ee3b0f1..4803aae68 100644 --- a/ospfd/subdir.am +++ b/ospfd/subdir.am @@ -56,6 +56,7 @@ ospfd_libfrrospf_a_SOURCES = \ ospfd/ospf_zebra.c \ ospfd/ospfd.c \ ospfd/ospf_gr_helper.c \ + ospfd/ospf_auth.c \ # end if OSPFD @@ -106,6 +107,7 @@ noinst_HEADERS += \ ospfd/ospf_te.h \ ospfd/ospf_vty.h \ ospfd/ospf_zebra.h \ + ospfd/ospf_auth.h \ # end ospfd_ospfd_LDADD = ospfd/libfrrospf.a ospfd/libfrrospfclient.a lib/libfrr.la $(LIBCAP) $(LIBM) diff --git a/python/xref2vtysh.py b/python/xref2vtysh.py index 6dd5c8866..0a7e28ec7 100644 --- a/python/xref2vtysh.py +++ b/python/xref2vtysh.py @@ -36,7 +36,7 @@ daemon_flags = { "lib/filter.c": "VTYSH_ACL", "lib/filter_cli.c": "VTYSH_ACL", "lib/if.c": "VTYSH_INTERFACE", - "lib/keychain.c": "VTYSH_RIPD|VTYSH_EIGRPD|VTYSH_OSPF6D", + "lib/keychain.c": "VTYSH_KEYS", "lib/mgmt_be_client.c": "VTYSH_STATICD", "lib/mgmt_fe_client.c": "VTYSH_MGMTD", "lib/lib_vty.c": "VTYSH_ALL", diff --git a/tests/topotests/lib/ospf.py b/tests/topotests/lib/ospf.py index 5486e904d..5b18f8b67 100644 --- a/tests/topotests/lib/ospf.py +++ b/tests/topotests/lib/ospf.py @@ -426,6 +426,10 @@ def config_ospf_interface( cmd = "ip ospf authentication null" elif data_ospf_auth == "message-digest": cmd = "ip ospf authentication message-digest" + elif data_ospf_auth == "key-chain": + cmd = "ip ospf authentication key-chain {}".format( + ospf_data["keychain"] + ) else: cmd = "ip ospf authentication" diff --git a/tests/topotests/ospf_basic_functionality/test_ospf_authentication.py b/tests/topotests/ospf_basic_functionality/test_ospf_authentication.py index 88219b840..8dd103013 100644 --- a/tests/topotests/ospf_basic_functionality/test_ospf_authentication.py +++ b/tests/topotests/ospf_basic_functionality/test_ospf_authentication.py @@ -64,7 +64,9 @@ TOPOOLOGY = TESTCASES = 1. Verify ospf authentication with Simple password authentication. 2. Verify ospf authentication with MD5 authentication. -3. Verify ospf authentication with different authentication methods. +3. Verify ospf authentication with MD5 keychain authentication. +4. Verify ospf authentication with SHA256 keychain authentication. +5. Verify ospf authentication with different authentication methods. """ @@ -535,7 +537,477 @@ def test_ospf_authentication_md5_tc29_p1(request): write_test_footer(tc_name) -def test_ospf_authentication_different_auths_tc30_p1(request): +def test_ospf_authentication_md5_keychain_tc30_p1(request): + """ + OSPF Authentication - Verify ospf authentication with MD5 authentication. + + """ + tc_name = request.node.name + write_test_header(tc_name) + tgen = get_topogen() + global topo + step("Bring up the base config.") + reset_config_on_routers(tgen) + step( + "Configure ospf with on R1 and R2, enable ospf on R1 interface " + "connected to R2 with message-digest authentication using ip " + "ospf authentication key-chain cmd." + ) + + router1 = tgen.gears["r1"] + router2 = tgen.gears["r2"] + + router1.vtysh_cmd( + """configure terminal + key chain auth + key 10 + key-string ospf + cryptographic-algorithm md5""" + ) + + r1_ospf_auth = { + "r1": { + "links": { + "r2": { + "ospf": { + "authentication": "key-chain", + "keychain": "auth", + } + } + } + } + } + result = config_ospf_interface(tgen, topo, r1_ospf_auth) + assert result is True, "Testcase {} :Failed \n Error: {}".format(tc_name, result) + + step("Verify that the neighbour is not FULL between R1 and R2.") + # wait for dead time expiry. + sleep(6) + dut = "r1" + ospf_covergence = verify_ospf_neighbor( + tgen, topo, dut=dut, expected=False, retry_timeout=6 + ) + assert ospf_covergence is not True, "Testcase Failed \n Error {}".format( + ospf_covergence + ) + + step( + "On R2 enable ospf on interface with message-digest authentication" + " using ip ospf authentication message-digest password cmd." + ) + + router2.vtysh_cmd( + """configure terminal + key chain auth + key 10 + key-string ospf + cryptographic-algorithm md5""" + ) + + r2_ospf_auth = { + "r2": { + "links": { + "r1": { + "ospf": { + "authentication": "key-chain", + "keychain": "auth", + } + } + } + } + } + result = config_ospf_interface(tgen, topo, r2_ospf_auth) + assert result is True, "Testcase {} :Failed \n Error: {}".format(tc_name, result) + + step( + "Verify that the neighbour is FULL between R1 and R2 " + "using show ip ospf neighbor cmd." + ) + + dut = "r2" + ospf_covergence = verify_ospf_neighbor(tgen, topo, dut=dut) + assert ospf_covergence is True, "Testcase Failed \n Error {}".format( + ospf_covergence + ) + + step( + "Disable message-digest authentication on R2 using no ip ospf " + "authentication key-chain cmd." + ) + + r2_ospf_auth = { + "r2": { + "links": { + "r1": { + "ospf": { + "authentication": "key-chain", + "keychain": "auth", + "del_action": True, + } + } + } + } + } + result = config_ospf_interface(tgen, topo, r2_ospf_auth) + assert result is True, "Testcase {} :Failed \n Error: {}".format(tc_name, result) + + step("Verify on R1 ,nbr is deleted for R2 after dead interval expiry") + # wait till the dead timer expiry + sleep(6) + dut = "r2" + ospf_covergence = verify_ospf_neighbor( + tgen, topo, dut=dut, expected=False, retry_timeout=10 + ) + assert ospf_covergence is not True, "Testcase Failed \n Error {}".format( + ospf_covergence + ) + + step("Again On R2 enable ospf on interface with key-chain auth") + r2_ospf_auth = { + "r2": { + "links": { + "r1": { + "ospf": { + "authentication": "key-chain", + "keychain": "auth", + } + } + } + } + } + result = config_ospf_interface(tgen, topo, r2_ospf_auth) + assert result is True, "Testcase {} :Failed \n Error: {}".format(tc_name, result) + + step( + "Verify that the neighbour is FULL between R1 and R2 using" + " show ip ospf neighbor cmd." + ) + + dut = "r2" + ospf_covergence = verify_ospf_neighbor(tgen, topo, dut=dut) + assert ospf_covergence is True, "Testcase Failed \n Error {}".format( + ospf_covergence + ) + + step("Shut no shut interface on R1") + dut = "r1" + intf = topo["routers"]["r1"]["links"]["r2"]["interface"] + shutdown_bringup_interface(tgen, dut, intf, False) + + dut = "r2" + step( + "Verify that the neighbour is not FULL between R1 and R2 using " + "show ip ospf neighbor cmd." + ) + ospf_covergence = verify_ospf_neighbor(tgen, topo, dut=dut, expected=False) + assert ospf_covergence is not True, "Testcase Failed \n Error {}".format( + ospf_covergence + ) + + dut = "r1" + shutdown_bringup_interface(tgen, dut, intf, True) + + step( + "Verify that the neighbour is FULL between R1 and R2 using " + "show ip ospf neighbor cmd." + ) + + dut = "r2" + ospf_covergence = verify_ospf_neighbor(tgen, topo, dut=dut) + assert ospf_covergence is True, "Testcase Failed \n Error {}".format( + ospf_covergence + ) + + step("Change Ip address on R1 and R2") + + topo_modify_change_ip = deepcopy(topo) + + intf_ip = topo_modify_change_ip["routers"]["r1"]["links"]["r2"]["ipv4"] + + topo_modify_change_ip["routers"]["r1"]["links"]["r2"]["ipv4"] = str( + IPv4Address(frr_unicode(intf_ip.split("/")[0])) + 3 + ) + "/{}".format(intf_ip.split("/")[1]) + + build_config_from_json(tgen, topo_modify_change_ip, save_bkup=False) + + reset_config_on_routers(tgen, routerName="r1") + dut = "r1" + intf = topo["routers"]["r1"]["links"]["r2"]["interface"] + shutdown_bringup_interface(tgen, dut, intf, False) + shutdown_bringup_interface(tgen, dut, intf, True) + clear_ospf(tgen, "r1") + router1.vtysh_cmd( + """configure terminal + key chain auth + key 10 + key-string ospf + cryptographic-algorithm md5""" + ) + r1_ospf_auth = { + "r1": { + "links": { + "r2": { + "ospf": { + "authentication": "key-chain", + "keychain": "auth", + } + } + } + } + } + result = config_ospf_interface(tgen, topo, r1_ospf_auth) + assert result is True, "Testcase {} :Failed \n Error: {}".format(tc_name, result) + + step( + "Verify that the neighbour is FULL between R1 and R2 with new " + "ip address using show ip ospf " + ) + + dut = "r1" + ospf_covergence = verify_ospf_neighbor(tgen, topo, dut=dut) + assert ospf_covergence is True, "Testcase Failed \n Error {}".format( + ospf_covergence + ) + + write_test_footer(tc_name) + + +def test_ospf_authentication_sha256_keychain_tc32_p1(request): + """ + OSPF Authentication - Verify ospf authentication with MD5 authentication. + + """ + tc_name = request.node.name + write_test_header(tc_name) + tgen = get_topogen() + global topo + step("Bring up the base config.") + reset_config_on_routers(tgen) + step( + "Configure ospf with on R1 and R2, enable ospf on R1 interface " + "connected to R2 with message-digest authentication using ip " + "ospf authentication key-chain cmd." + ) + + router1 = tgen.gears["r1"] + router2 = tgen.gears["r2"] + + router1.vtysh_cmd( + """configure terminal + key chain auth + key 10 + key-string ospf + cryptographic-algorithm hmac-sha-256""" + ) + + r1_ospf_auth = { + "r1": { + "links": { + "r2": { + "ospf": { + "authentication": "key-chain", + "keychain": "auth", + } + } + } + } + } + result = config_ospf_interface(tgen, topo, r1_ospf_auth) + assert result is True, "Testcase {} :Failed \n Error: {}".format(tc_name, result) + + step("Verify that the neighbour is not FULL between R1 and R2.") + # wait for dead time expiry. + sleep(6) + dut = "r1" + ospf_covergence = verify_ospf_neighbor( + tgen, topo, dut=dut, expected=False, retry_timeout=6 + ) + assert ospf_covergence is not True, "Testcase Failed \n Error {}".format( + ospf_covergence + ) + + step( + "On R2 enable ospf on interface with message-digest authentication" + " using ip ospf authentication message-digest password cmd." + ) + + router2.vtysh_cmd( + """configure terminal + key chain auth + key 10 + key-string ospf + cryptographic-algorithm hmac-sha-256""" + ) + + r2_ospf_auth = { + "r2": { + "links": { + "r1": { + "ospf": { + "authentication": "key-chain", + "keychain": "auth", + } + } + } + } + } + result = config_ospf_interface(tgen, topo, r2_ospf_auth) + assert result is True, "Testcase {} :Failed \n Error: {}".format(tc_name, result) + + step( + "Verify that the neighbour is FULL between R1 and R2 " + "using show ip ospf neighbor cmd." + ) + + dut = "r2" + ospf_covergence = verify_ospf_neighbor(tgen, topo, dut=dut) + assert ospf_covergence is True, "Testcase Failed \n Error {}".format( + ospf_covergence + ) + + step( + "Disable message-digest authentication on R2 using no ip ospf " + "authentication key-chain cmd." + ) + + r2_ospf_auth = { + "r2": { + "links": { + "r1": { + "ospf": { + "authentication": "key-chain", + "keychain": "auth", + "del_action": True, + } + } + } + } + } + result = config_ospf_interface(tgen, topo, r2_ospf_auth) + assert result is True, "Testcase {} :Failed \n Error: {}".format(tc_name, result) + + step("Verify on R1 ,nbr is deleted for R2 after dead interval expiry") + # wait till the dead timer expiry + sleep(6) + dut = "r2" + ospf_covergence = verify_ospf_neighbor( + tgen, topo, dut=dut, expected=False, retry_timeout=10 + ) + assert ospf_covergence is not True, "Testcase Failed \n Error {}".format( + ospf_covergence + ) + + step("Again On R2 enable ospf on interface with key-chain auth") + r2_ospf_auth = { + "r2": { + "links": { + "r1": { + "ospf": { + "authentication": "key-chain", + "keychain": "auth", + } + } + } + } + } + result = config_ospf_interface(tgen, topo, r2_ospf_auth) + assert result is True, "Testcase {} :Failed \n Error: {}".format(tc_name, result) + + step( + "Verify that the neighbour is FULL between R1 and R2 using" + " show ip ospf neighbor cmd." + ) + + dut = "r2" + ospf_covergence = verify_ospf_neighbor(tgen, topo, dut=dut) + assert ospf_covergence is True, "Testcase Failed \n Error {}".format( + ospf_covergence + ) + + step("Shut no shut interface on R1") + dut = "r1" + intf = topo["routers"]["r1"]["links"]["r2"]["interface"] + shutdown_bringup_interface(tgen, dut, intf, False) + + dut = "r2" + step( + "Verify that the neighbour is not FULL between R1 and R2 using " + "show ip ospf neighbor cmd." + ) + ospf_covergence = verify_ospf_neighbor(tgen, topo, dut=dut, expected=False) + assert ospf_covergence is not True, "Testcase Failed \n Error {}".format( + ospf_covergence + ) + + dut = "r1" + shutdown_bringup_interface(tgen, dut, intf, True) + + step( + "Verify that the neighbour is FULL between R1 and R2 using " + "show ip ospf neighbor cmd." + ) + + dut = "r2" + ospf_covergence = verify_ospf_neighbor(tgen, topo, dut=dut) + assert ospf_covergence is True, "Testcase Failed \n Error {}".format( + ospf_covergence + ) + + step("Change Ip address on R1 and R2") + + topo_modify_change_ip = deepcopy(topo) + + intf_ip = topo_modify_change_ip["routers"]["r1"]["links"]["r2"]["ipv4"] + + topo_modify_change_ip["routers"]["r1"]["links"]["r2"]["ipv4"] = str( + IPv4Address(frr_unicode(intf_ip.split("/")[0])) + 3 + ) + "/{}".format(intf_ip.split("/")[1]) + + build_config_from_json(tgen, topo_modify_change_ip, save_bkup=False) + + reset_config_on_routers(tgen, routerName="r1") + dut = "r1" + intf = topo["routers"]["r1"]["links"]["r2"]["interface"] + shutdown_bringup_interface(tgen, dut, intf, False) + shutdown_bringup_interface(tgen, dut, intf, True) + clear_ospf(tgen, "r1") + router1.vtysh_cmd( + """configure terminal + key chain auth + key 10 + key-string ospf + cryptographic-algorithm hmac-sha-256""" + ) + r1_ospf_auth = { + "r1": { + "links": { + "r2": { + "ospf": { + "authentication": "key-chain", + "keychain": "auth", + } + } + } + } + } + result = config_ospf_interface(tgen, topo, r1_ospf_auth) + assert result is True, "Testcase {} :Failed \n Error: {}".format(tc_name, result) + + step( + "Verify that the neighbour is FULL between R1 and R2 with new " + "ip address using show ip ospf " + ) + + dut = "r1" + ospf_covergence = verify_ospf_neighbor(tgen, topo, dut=dut) + assert ospf_covergence is True, "Testcase Failed \n Error {}".format( + ospf_covergence + ) + + write_test_footer(tc_name) + + +def test_ospf_authentication_different_auths_tc35_p1(request): """ OSPF Authentication - Verify ospf authentication with different authentication methods. @@ -553,6 +1025,9 @@ def test_ospf_authentication_different_auths_tc30_p1(request): "ospf authentication message-digest cmd." ) + router1 = tgen.gears["r1"] + router2 = tgen.gears["r2"] + r1_ospf_auth = { "r1": { "links": { @@ -769,16 +1244,23 @@ def test_ospf_authentication_different_auths_tc30_p1(request): ospf_covergence ) - step("Enable Md5 authentication on the interface") + step("Enable SHA-256 authentication on the interface") + + router1.vtysh_cmd( + """configure terminal + key chain auth + key 10 + key-string ospf + cryptographic-algorithm hmac-sha-256""" + ) r1_ospf_auth = { "r1": { "links": { "r2": { "ospf": { - "authentication": "message-digest", - "authentication-key": "ospf", - "message-digest-key": "10", + "authentication": "key-chain", + "keychain": "auth", } } } @@ -787,14 +1269,21 @@ def test_ospf_authentication_different_auths_tc30_p1(request): result = config_ospf_interface(tgen, topo, r1_ospf_auth) assert result is True, "Testcase {} :Failed \n Error: {}".format(tc_name, result) + router2.vtysh_cmd( + """configure terminal + key chain auth + key 10 + key-string ospf + cryptographic-algorithm hmac-sha-256""" + ) + r2_ospf_auth = { "r2": { "links": { "r1": { "ospf": { - "authentication": "message-digest", - "authentication-key": "ospf", - "message-digest-key": "10", + "authentication": "key-chain", + "keychain": "auth", } } } @@ -814,39 +1303,27 @@ def test_ospf_authentication_different_auths_tc30_p1(request): ospf_covergence ) - step("Change the MD5 authentication password") + step("Change the SHA-256 authentication password") - r1_ospf_auth = { - "r1": { - "links": { - "r2": { - "ospf": { - "authentication": "message-digest", - "authentication-key": "OSPFv4", - "message-digest-key": "10", - } - } - } - } - } - result = config_ospf_interface(tgen, topo, r1_ospf_auth) - assert result is True, "Testcase {} :Failed \n Error: {}".format(tc_name, result) + router1.vtysh_cmd( + """configure terminal + key chain auth + key 10 + key-string OSPFv4 + cryptographic-algorithm hmac-sha-512""" + ) - r2_ospf_auth = { - "r2": { - "links": { - "r1": { - "ospf": { - "authentication": "message-digest", - "authentication-key": "OSPFv4", - "message-digest-key": "10", - } - } - } - } - } - result = config_ospf_interface(tgen, topo, r2_ospf_auth) - assert result is True, "Testcase {} :Failed \n Error: {}".format(tc_name, result) + router2.vtysh_cmd( + """configure terminal + key chain auth + key 10 + key-string OSPFv4 + cryptographic-algorithm hmac-sha-512""" + ) + ospf_covergence = verify_ospf_neighbor(tgen, topo, dut=dut) + assert ospf_covergence is True, "Testcase Failed \n Error {}".format( + ospf_covergence + ) write_test_footer(tc_name) diff --git a/vtysh/vtysh.h b/vtysh/vtysh.h index 9d67003f3..65733ca61 100644 --- a/vtysh/vtysh.h +++ b/vtysh/vtysh.h @@ -59,7 +59,7 @@ extern struct event_loop *master; VTYSH_VRRPD #define VTYSH_INTERFACE VTYSH_INTERFACE_SUBSET | VTYSH_BGPD #define VTYSH_VRF VTYSH_INTERFACE_SUBSET | VTYSH_MGMTD -#define VTYSH_KEYS VTYSH_RIPD | VTYSH_EIGRPD | VTYSH_OSPF6D +#define VTYSH_KEYS VTYSH_RIPD | VTYSH_EIGRPD | VTYSH_OSPF6D | VTYSH_OSPFD /* Daemons who can process nexthop-group configs */ #define VTYSH_NH_GROUP VTYSH_PBRD|VTYSH_SHARPD #define VTYSH_SR VTYSH_ZEBRA|VTYSH_PATHD -- cgit v1.2.3