// -*- c-basic-offset: 4; tab-width: 8; indent-tabs-mode: t -*- // Copyright (c) 2001-2007 International Computer Science Institute // // Permission is hereby granted, free of charge, to any person obtaining a // copy of this software and associated documentation files (the "Software") // to deal in the Software without restriction, subject to the conditions // listed in the XORP LICENSE file. These conditions include: you must // preserve this copyright notice, and you cannot mention the copyright // holders in advertising related to the Software without their permission. // The Software is provided WITHOUT ANY WARRANTY, EXPRESS OR IMPLIED. This // notice is a summary of the XORP LICENSE file; the license in that file is // legally binding. #ident "$XORP: xorp/rip/auth.cc,v 1.36 2007/02/16 22:47:13 pavlin Exp $" #include "rip_module.h" #include "libxorp/xorp.h" #include "libxorp/xlog.h" #include "libxorp/eventloop.hh" #include #include #include "constants.hh" #include "auth.hh" // ---------------------------------------------------------------------------- // AuthHandlerBase implementation AuthHandlerBase::~AuthHandlerBase() { } const string& AuthHandlerBase::error() const { return _err; } inline void AuthHandlerBase::reset_error() { if (_err.empty() == false) _err.erase(); } inline void AuthHandlerBase::set_error(const string& err) { _err = err; } // ---------------------------------------------------------------------------- // NullAuthHandler implementation const char* NullAuthHandler::effective_name() const { return auth_type_name(); } const char* NullAuthHandler::auth_type_name() { return "none"; } void NullAuthHandler::reset() { } uint32_t NullAuthHandler::head_entries() const { return 0; } uint32_t NullAuthHandler::max_routing_entries() const { return RIPv2_ROUTES_PER_PACKET; } bool NullAuthHandler::authenticate_inbound(const uint8_t* packet, size_t packet_bytes, const uint8_t*& entries_ptr, uint32_t& n_entries, const IPv4&, bool) { entries_ptr = NULL; n_entries = 0; if (packet_bytes > RIPv2_MAX_PACKET_BYTES) { set_error(c_format("packet too large (%u bytes)", XORP_UINT_CAST(packet_bytes))); return false; } else if (packet_bytes < RIPv2_MIN_PACKET_BYTES) { set_error(c_format("packet too small (%u bytes)", XORP_UINT_CAST(packet_bytes))); return false; } size_t entry_bytes = packet_bytes - RipPacketHeader::size(); if (entry_bytes % PacketRouteEntry::size()) { set_error(c_format("non-integral route entries (%u bytes)", XORP_UINT_CAST(entry_bytes))); return false; } n_entries = entry_bytes / PacketRouteEntry::size(); if (n_entries == 0) { return true; } entries_ptr = packet + RipPacketHeader::size(); const PacketRouteEntry entry(entries_ptr); // Reject packet if first entry is authentication data if (entry.is_auth_entry()) { set_error(c_format("unexpected authentication data (type %d)", entry.tag())); entries_ptr = NULL; n_entries = 0; return false; } reset_error(); return true; } bool NullAuthHandler::authenticate_outbound(RipPacket& packet, list *>& auth_packets, size_t& n_routes) { // XXX: nothing to do so just create a single copy RipPacket* copy_packet = new RipPacket(packet); auth_packets.push_back(copy_packet); reset_error(); n_routes = packet.data_bytes() - RipPacketHeader::size(); n_routes /= PacketRouteEntry::size(); return (true); } // ---------------------------------------------------------------------------- // Plaintext handler implementation const char* PlaintextAuthHandler::effective_name() const { return auth_type_name(); } const char* PlaintextAuthHandler::auth_type_name() { return "simple"; } void PlaintextAuthHandler::reset() { } uint32_t PlaintextAuthHandler::head_entries() const { return 1; } uint32_t PlaintextAuthHandler::max_routing_entries() const { return RIPv2_ROUTES_PER_PACKET - 1; } bool PlaintextAuthHandler::authenticate_inbound(const uint8_t* packet, size_t packet_bytes, const uint8_t*& entries_ptr, uint32_t& n_entries, const IPv4&, bool) { entries_ptr = NULL; n_entries = 0; if (packet_bytes > RIPv2_MAX_PACKET_BYTES) { set_error(c_format("packet too large (%u bytes)", XORP_UINT_CAST(packet_bytes))); return false; } if (packet_bytes < RIPv2_MIN_AUTH_PACKET_BYTES) { set_error(c_format("packet too small (%u bytes)", XORP_UINT_CAST(packet_bytes))); return false; } size_t entry_bytes = packet_bytes - RipPacketHeader::size(); if (entry_bytes % PacketRouteEntry::size()) { set_error(c_format("non-integral route entries (%u bytes)", XORP_UINT_CAST(entry_bytes))); return false; } const PlaintextPacketRouteEntry4 ppr(packet + RipPacketHeader::size()); if (ppr.addr_family() != PlaintextPacketRouteEntry4::ADDR_FAMILY) { set_error("not an authenticated packet"); return false; } else if (ppr.auth_type() != PlaintextPacketRouteEntry4::AUTH_TYPE) { set_error("not a plaintext authenticated packet"); return false; } string passwd = ppr.password(); if (passwd != key()) { set_error(c_format("wrong password \"%s\"", passwd.c_str())); return false; } reset_error(); n_entries = entry_bytes / PacketRouteEntry::size() - 1; if (n_entries) { entries_ptr = (packet + RipPacketHeader::size() + PlaintextPacketRouteEntry4::size()); } return true; } bool PlaintextAuthHandler::authenticate_outbound(RipPacket& packet, list *>& auth_packets, size_t& n_routes) { uint8_t* first_entry_ptr = NULL; if (head_entries() > 0) first_entry_ptr = packet.route_entry_ptr(0); static_assert(PacketRouteEntry::SIZE == 20); static_assert(PlaintextPacketRouteEntry4::SIZE == 20); XLOG_ASSERT(packet.data_ptr() + RipPacketHeader::size() == first_entry_ptr); PlaintextPacketRouteEntry4Writer ppr(first_entry_ptr); ppr.initialize(key()); // XXX: just create a single copy RipPacket* copy_packet = new RipPacket(packet); auth_packets.push_back(copy_packet); reset_error(); n_routes = packet.data_bytes() - RipPacketHeader::size(); n_routes /= PacketRouteEntry::size(); n_routes--; // XXX: exclude the first (authentication) entry return (true); } const string& PlaintextAuthHandler::key() const { return _key; } void PlaintextAuthHandler::set_key(const string& plaintext_key) { _key = string(plaintext_key, 0, 16); } // ---------------------------------------------------------------------------- // MD5AuthHandler::MD5Key implementation MD5AuthHandler::MD5Key::MD5Key(uint8_t key_id, const string& key, const TimeVal& start_timeval, const TimeVal& end_timeval, XorpTimer start_timer, XorpTimer stop_timer) : _id(key_id), _start_timeval(start_timeval), _end_timeval(end_timeval), _is_persistent(false), _o_seqno(0), _start_timer(start_timer), _stop_timer(stop_timer) { string::size_type n = key.copy(_key_data, 16); if (n < KEY_BYTES) { memset(_key_data + n, 0, KEY_BYTES - n); } } string MD5AuthHandler::MD5Key::key() const { return string(_key_data, 0, 16); } bool MD5AuthHandler::MD5Key::valid_at(const TimeVal& when) const { if (is_persistent()) return true; return ((_start_timeval <= when) && (when <= _end_timeval)); } void MD5AuthHandler::MD5Key::reset() { // // Reset the seqno // _lr_seqno.clear(); // // Reset the flag that a packet has been received // _pkts_recv.clear(); } void MD5AuthHandler::MD5Key::reset(const IPv4& src_addr) { map::iterator seqno_iter; map::iterator recv_iter; // // Reset the seqno // seqno_iter = _lr_seqno.find(src_addr); if (seqno_iter != _lr_seqno.end()) _lr_seqno.erase(seqno_iter); // // Reset the flag that a packet has been received // recv_iter = _pkts_recv.find(src_addr); if (recv_iter != _pkts_recv.end()) _pkts_recv.erase(recv_iter); } bool MD5AuthHandler::MD5Key::packets_received(const IPv4& src_addr) const { map::const_iterator iter; iter = _pkts_recv.find(src_addr); if (iter == _pkts_recv.end()) return (false); return (iter->second); } uint32_t MD5AuthHandler::MD5Key::last_seqno_recv(const IPv4& src_addr) const { map::const_iterator iter; iter = _lr_seqno.find(src_addr); if (iter == _lr_seqno.end()) return (0); return (iter->second); } void MD5AuthHandler::MD5Key::set_last_seqno_recv(const IPv4& src_addr, uint32_t seqno) { map::iterator seqno_iter; map::iterator recv_iter; // // Set the seqno // seqno_iter = _lr_seqno.find(src_addr); if (seqno_iter == _lr_seqno.end()) _lr_seqno.insert(make_pair(src_addr, seqno)); else seqno_iter->second = seqno; // // Set the flag that a packet has been received // recv_iter = _pkts_recv.find(src_addr); if (recv_iter == _pkts_recv.end()) _pkts_recv.insert(make_pair(src_addr, true)); else recv_iter->second = true; } // ---------------------------------------------------------------------------- // MD5AuthHandler implementation MD5AuthHandler::MD5AuthHandler(EventLoop& eventloop) : _eventloop(eventloop) { } const char* MD5AuthHandler::effective_name() const { // // XXX: if no valid keys, then don't use any authentication // if (_valid_key_chain.empty()) { return (_null_handler.effective_name()); } return auth_type_name(); } const char* MD5AuthHandler::auth_type_name() { return "md5"; } void MD5AuthHandler::reset() { // // XXX: if no valid keys, then don't use any authentication // if (_valid_key_chain.empty()) { _null_handler.reset(); return; } reset_keys(); } uint32_t MD5AuthHandler::head_entries() const { // // XXX: if no valid keys, then don't use any authentication // if (_valid_key_chain.empty()) { return (_null_handler.head_entries()); } return 1; } uint32_t MD5AuthHandler::max_routing_entries() const { // // XXX: if no valid keys, then don't use any authentication // if (_valid_key_chain.empty()) { return (_null_handler.max_routing_entries()); } return RIPv2_ROUTES_PER_PACKET - 1; } bool MD5AuthHandler::authenticate_inbound(const uint8_t* packet, size_t packet_bytes, const uint8_t*& entries_ptr, uint32_t& n_entries, const IPv4& src_addr, bool new_peer) { static_assert(MD5PacketTrailer::SIZE == 20); // // XXX: if no valid keys, then don't use any authentication // if (_valid_key_chain.empty()) { if (_null_handler.authenticate_inbound(packet, packet_bytes, entries_ptr, n_entries, src_addr, new_peer) != true) { set_error(_null_handler.error()); return (false); } reset_error(); return (true); } entries_ptr = NULL; n_entries = 0; if (packet_bytes > RIPv2_MAX_PACKET_BYTES + MD5PacketTrailer::size()) { set_error(c_format("packet too large (%u bytes)", XORP_UINT_CAST(packet_bytes))); return false; } if (packet_bytes < RIPv2_MIN_AUTH_PACKET_BYTES) { set_error(c_format("packet too small (%u bytes)", XORP_UINT_CAST(packet_bytes))); return false; } const MD5PacketRouteEntry4 mpr(packet + RipPacketHeader::size()); if (mpr.addr_family() != MD5PacketRouteEntry4::ADDR_FAMILY) { set_error("not an authenticated packet"); return false; } if (mpr.auth_type() != MD5PacketRouteEntry4::AUTH_TYPE) { set_error("not an MD5 authenticated packet"); return false; } if (mpr.auth_bytes() != MD5PacketTrailer::size()) { set_error(c_format("wrong number of auth bytes (%d != %u)", mpr.auth_bytes(), XORP_UINT_CAST(MD5PacketTrailer::size()))); return false; } if (uint32_t(mpr.auth_off() + mpr.auth_bytes()) != packet_bytes) { set_error(c_format("Size of packet does not correspond with " "authentication data offset and size " "(%d + %d != %u).", mpr.auth_off(), mpr.auth_bytes(), XORP_UINT_CAST(packet_bytes))); return false; } KeyChain::iterator k = find_if(_valid_key_chain.begin(), _valid_key_chain.end(), bind2nd(mem_fun_ref(&MD5Key::id_matches), mpr.key_id())); if (k == _valid_key_chain.end()) { set_error(c_format("packet with key ID %d for which no key is " "configured", mpr.key_id())); return false; } MD5Key* key = &(*k); if (new_peer) key->reset(src_addr); uint32_t last_seqno_recv = key->last_seqno_recv(src_addr); if (key->packets_received(src_addr) && !(new_peer && mpr.seqno() == 0) && (mpr.seqno() - last_seqno_recv >= 0x7fffffff)) { set_error(c_format("bad sequence number 0x%08x < 0x%08x", XORP_UINT_CAST(mpr.seqno()), XORP_UINT_CAST(last_seqno_recv))); return false; } const MD5PacketTrailer mpt(packet + mpr.auth_off()); if (mpt.valid() == false) { set_error("invalid authentication trailer"); return false; } MD5_CTX ctx; uint8_t digest[16]; MD5_Init(&ctx); MD5_Update(&ctx, packet, mpr.auth_off() + mpt.auth_data_offset()); MD5_Update(&ctx, key->key_data(), key->key_data_bytes()); MD5_Final(digest, &ctx); if (memcmp(digest, mpt.auth_data(), mpt.auth_data_bytes()) != 0) { set_error(c_format("authentication digest doesn't match local key " "(key ID = %d)", key->id())); // #define DUMP_BAD_MD5 #ifdef DUMP_BAD_MD5 const char badmd5[] = "/tmp/rip_badmd5"; // If the file already exists don't dump anything. The file // should contain and only one packet. if (-1 == access(badmd5, R_OK)) { XLOG_INFO("Dumping bad MD5 to %s", badmd5); FILE *fp = fopen(badmd5, "w"); fwrite(packet, packet_bytes, 1 , fp); fclose(fp); } #endif return false; } // Update sequence number only after packet has passed digest check key->set_last_seqno_recv(src_addr, mpr.seqno()); reset_error(); n_entries = (mpr.auth_off() - RipPacketHeader::size()) / PacketRouteEntry::size() - 1; if (n_entries > 0) { entries_ptr = (packet + RipPacketHeader::size() + MD5PacketRouteEntry4::size()); } return true; } bool MD5AuthHandler::authenticate_outbound(RipPacket& packet, list *>& auth_packets, size_t& n_routes) { RipPacket first_packet(packet); vector first_trailer; KeyChain::iterator iter; static_assert(MD5PacketTrailer::SIZE == 20); // // XXX: if no valid keys, then don't use any authentication // if (_valid_key_chain.empty()) { if (_null_handler.authenticate_outbound(packet, auth_packets, n_routes) != true) { set_error(_null_handler.error()); return (false); } reset_error(); return (true); } // // Create an authenticated copy of the packet for each valid key // for (iter = _valid_key_chain.begin(); iter != _valid_key_chain.end(); ++iter) { MD5Key& key = *iter; RipPacket* copy_packet = new RipPacket(packet); auth_packets.push_back(copy_packet); uint8_t* first_entry_ptr = NULL; if (head_entries() > 0) first_entry_ptr = copy_packet->route_entry_ptr(0); MD5PacketRouteEntry4Writer mpr(first_entry_ptr); mpr.initialize(copy_packet->data_bytes(), key.id(), MD5PacketTrailer::size(), key.next_seqno_out()); vector trailer; trailer.resize(MD5PacketTrailer::size()); MD5PacketTrailerWriter mpt(&trailer[0]); mpt.initialize(); MD5_CTX ctx; MD5_Init(&ctx); MD5_Update(&ctx, copy_packet->data_ptr(), mpr.auth_off()); MD5_Update(&ctx, &trailer[0], mpt.auth_data_offset()); MD5_Update(&ctx, key.key_data(), key.key_data_bytes()); MD5_Final(mpt.auth_data(), &ctx); // // XXX: create a copy of the first packet without the trailer // and of the trailer itself. // if (iter == _valid_key_chain.begin()) { first_packet = *copy_packet; first_trailer = trailer; } copy_packet->append_data(trailer); } packet = first_packet; n_routes = packet.data_bytes() / MD5PacketRouteEntry4::size() - 1; packet.append_data(first_trailer); reset_error(); return (true); } bool MD5AuthHandler::add_key(uint8_t key_id, const string& key, const TimeVal& start_timeval, const TimeVal& end_timeval, string& error_msg) { TimeVal now; XorpTimer start_timer, end_timer; string dummy_error_msg; _eventloop.current_time(now); if (start_timeval > end_timeval) { error_msg = c_format("Start time is later than the end time"); return false; } if (end_timeval < now) { error_msg = c_format("End time is in the past"); return false; } if (start_timeval > now) { start_timer = _eventloop.new_oneoff_at( start_timeval, callback(this, &MD5AuthHandler::key_start_cb, key_id)); } if (end_timeval != TimeVal::MAXIMUM()) { end_timer = _eventloop.new_oneoff_at( end_timeval, callback(this, &MD5AuthHandler::key_stop_cb, key_id)); } // // XXX: If we are using the last authentication key that has expired, // move it to the list of invalid keys. // if (_valid_key_chain.size() == 1) { MD5Key& key = _valid_key_chain.front(); if (key.is_persistent()) { key.set_persistent(false); _invalid_key_chain.push_back(key); _valid_key_chain.pop_front(); } } // XXX: for simplicity just try to remove the key even if it doesn't exist remove_key(key_id, dummy_error_msg); // Add the new key to the appropriate chain MD5Key new_key = MD5Key(key_id, key, start_timeval, end_timeval, start_timer, end_timer); if (start_timer.scheduled()) _invalid_key_chain.push_back(new_key); else _valid_key_chain.push_back(new_key); return true; } bool MD5AuthHandler::remove_key(uint8_t key_id, string& error_msg) { KeyChain::iterator i; // Check among all valid keys i = find_if(_valid_key_chain.begin(), _valid_key_chain.end(), bind2nd(mem_fun_ref(&MD5Key::id_matches), key_id)); if (i != _valid_key_chain.end()) { _valid_key_chain.erase(i); return true; } // Check among all invalid keys i = find_if(_invalid_key_chain.begin(), _invalid_key_chain.end(), bind2nd(mem_fun_ref(&MD5Key::id_matches), key_id)); if (i != _invalid_key_chain.end()) { _invalid_key_chain.erase(i); return true; } error_msg = c_format("No such key"); return false; } void MD5AuthHandler::key_start_cb(uint8_t key_id) { KeyChain::iterator i; // Find the key among all invalid keys and move it to the valid keys i = find_if(_invalid_key_chain.begin(), _invalid_key_chain.end(), bind2nd(mem_fun_ref(&MD5Key::id_matches), key_id)); if (i != _invalid_key_chain.end()) { MD5Key& key = *i; _valid_key_chain.push_back(key); _invalid_key_chain.erase(i); } } void MD5AuthHandler::key_stop_cb(uint8_t key_id) { KeyChain::iterator i; // Find the key among all valid keys and move it to the invalid keys i = find_if(_valid_key_chain.begin(), _valid_key_chain.end(), bind2nd(mem_fun_ref(&MD5Key::id_matches), key_id)); if (i != _valid_key_chain.end()) { MD5Key& key = *i; // // XXX: If the last key expires then keep using it as per // RFC 2082 Section 4.3 until the lifetime is extended, the key // is deleted by network management, or a new key is configured. // if (_valid_key_chain.size() == 1) { XLOG_WARNING("Last authentication key (key ID = %u) has expired. " "Will keep using it until its lifetime is extended, " "the key is deleted, or a new key is configured.", key_id); key.set_persistent(true); return; } _invalid_key_chain.push_back(key); _valid_key_chain.erase(i); } } void MD5AuthHandler::reset_keys() { KeyChain::iterator iter; for (iter = _valid_key_chain.begin(); iter != _valid_key_chain.end(); ++iter) { iter->reset(); } } bool MD5AuthHandler::empty() const { return (_valid_key_chain.empty() && _invalid_key_chain.empty()); }