use hmac::{Hmac, Mac};
use md5::{Digest, Md5};
use sha1::Sha1;
use sha2::{Sha256, Sha384, Sha512};
use crate::protocols::ospf::constants::OSPF_AUTH_LEN;
pub const OSPF_MD5_DIGEST_LEN: u8 = 16;
pub const OSPF_HMAC_SHA1_DIGEST_LEN: u8 = 20;
pub const OSPF_HMAC_SHA256_DIGEST_LEN: u8 = 32;
pub const OSPF_HMAC_SHA384_DIGEST_LEN: u8 = 48;
pub const OSPF_HMAC_SHA512_DIGEST_LEN: u8 = 64;
const OSPF_MD5_KEY_PAD_LEN: usize = 16;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum OspfCryptoAlgorithm {
KeyedMd5,
HmacSha1,
HmacSha256,
HmacSha384,
HmacSha512,
}
impl OspfCryptoAlgorithm {
pub fn digest_len(self) -> u8 {
match self {
OspfCryptoAlgorithm::KeyedMd5 => OSPF_MD5_DIGEST_LEN,
OspfCryptoAlgorithm::HmacSha1 => OSPF_HMAC_SHA1_DIGEST_LEN,
OspfCryptoAlgorithm::HmacSha256 => OSPF_HMAC_SHA256_DIGEST_LEN,
OspfCryptoAlgorithm::HmacSha384 => OSPF_HMAC_SHA384_DIGEST_LEN,
OspfCryptoAlgorithm::HmacSha512 => OSPF_HMAC_SHA512_DIGEST_LEN,
}
}
}
#[derive(Debug, Clone)]
pub struct OspfCryptoAuth {
algorithm: OspfCryptoAlgorithm,
key_id: u8,
sequence_number: u32,
key: Vec<u8>,
}
impl OspfCryptoAuth {
pub fn new(key_id: u8, sequence_number: u32, key: impl Into<Vec<u8>>) -> Self {
Self::with_algorithm(OspfCryptoAlgorithm::KeyedMd5, key_id, sequence_number, key)
}
pub fn with_algorithm(
algorithm: OspfCryptoAlgorithm,
key_id: u8,
sequence_number: u32,
key: impl Into<Vec<u8>>,
) -> Self {
Self {
algorithm,
key_id,
sequence_number,
key: key.into(),
}
}
pub fn algorithm(&self) -> OspfCryptoAlgorithm {
self.algorithm
}
pub fn key_id(&self) -> u8 {
self.key_id
}
pub fn sequence_number(&self) -> u32 {
self.sequence_number
}
pub fn key(&self) -> &[u8] {
&self.key
}
pub fn digest_len(&self) -> u8 {
self.algorithm.digest_len()
}
pub(crate) fn structured_auth_field(&self) -> [u8; OSPF_AUTH_LEN] {
let mut field = [0u8; OSPF_AUTH_LEN];
field[2] = self.key_id;
field[3] = self.algorithm.digest_len();
field[4..8].copy_from_slice(&self.sequence_number.to_be_bytes());
field
}
pub(crate) fn digest(&self, packet_bytes: &[u8]) -> Vec<u8> {
match self.algorithm {
OspfCryptoAlgorithm::KeyedMd5 => self.md5_digest(packet_bytes).to_vec(),
OspfCryptoAlgorithm::HmacSha1 => hmac_sha1(&self.key, packet_bytes),
OspfCryptoAlgorithm::HmacSha256 => hmac_sha256(&self.key, packet_bytes),
OspfCryptoAlgorithm::HmacSha384 => hmac_sha384(&self.key, packet_bytes),
OspfCryptoAlgorithm::HmacSha512 => hmac_sha512(&self.key, packet_bytes),
}
}
pub(crate) fn md5_digest(&self, packet_bytes: &[u8]) -> [u8; OSPF_MD5_DIGEST_LEN as usize] {
let mut padded_key = [0u8; OSPF_MD5_KEY_PAD_LEN];
let copied = self.key.len().min(OSPF_MD5_KEY_PAD_LEN);
padded_key[..copied].copy_from_slice(&self.key[..copied]);
let mut hasher = Md5::new();
hasher.update(packet_bytes);
hasher.update(padded_key);
hasher.finalize().into()
}
}
fn hmac_sha1(key: &[u8], message: &[u8]) -> Vec<u8> {
let mut mac =
<Hmac<Sha1> as Mac>::new_from_slice(key).expect("HMAC accepts a key of any length");
mac.update(message);
mac.finalize().into_bytes().to_vec()
}
fn hmac_sha256(key: &[u8], message: &[u8]) -> Vec<u8> {
let mut mac =
<Hmac<Sha256> as Mac>::new_from_slice(key).expect("HMAC accepts a key of any length");
mac.update(message);
mac.finalize().into_bytes().to_vec()
}
fn hmac_sha384(key: &[u8], message: &[u8]) -> Vec<u8> {
let mut mac =
<Hmac<Sha384> as Mac>::new_from_slice(key).expect("HMAC accepts a key of any length");
mac.update(message);
mac.finalize().into_bytes().to_vec()
}
fn hmac_sha512(key: &[u8], message: &[u8]) -> Vec<u8> {
let mut mac =
<Hmac<Sha512> as Mac>::new_from_slice(key).expect("HMAC accepts a key of any length");
mac.update(message);
mac.finalize().into_bytes().to_vec()
}
pub(crate) fn simple_password_field(password: &[u8]) -> [u8; OSPF_AUTH_LEN] {
let mut field = [0u8; OSPF_AUTH_LEN];
let copied = password.len().min(OSPF_AUTH_LEN);
field[..copied].copy_from_slice(&password[..copied]);
field
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn ospf_simple_password_field_right_pads_short_passwords() {
assert_eq!(
simple_password_field(b"abc"),
[b'a', b'b', b'c', 0, 0, 0, 0, 0]
);
}
#[test]
fn ospf_simple_password_field_keeps_an_exact_eight_octet_password() {
assert_eq!(
simple_password_field(b"password"),
[b'p', b'a', b's', b's', b'w', b'o', b'r', b'd']
);
}
#[test]
fn ospf_simple_password_field_truncates_overlong_passwords() {
assert_eq!(
simple_password_field(b"toolongsecret"),
[b't', b'o', b'o', b'l', b'o', b'n', b'g', b's']
);
}
#[test]
fn ospf_crypto_auth_structured_field_matches_rfc_2328_d3_layout() {
let auth = OspfCryptoAuth::new(7, 0x0102_0304, b"secret".to_vec());
assert_eq!(
auth.structured_auth_field(),
[
0x00,
0x00,
0x07,
OSPF_MD5_DIGEST_LEN,
0x01,
0x02,
0x03,
0x04
]
);
}
#[test]
fn ospf_crypto_algorithm_digest_len_matches_rfc_5709() {
assert_eq!(OspfCryptoAlgorithm::KeyedMd5.digest_len(), 16);
assert_eq!(OspfCryptoAlgorithm::HmacSha1.digest_len(), 20);
assert_eq!(OspfCryptoAlgorithm::HmacSha256.digest_len(), 32);
assert_eq!(OspfCryptoAlgorithm::HmacSha384.digest_len(), 48);
assert_eq!(OspfCryptoAlgorithm::HmacSha512.digest_len(), 64);
}
#[test]
fn ospf_crypto_auth_structured_field_carries_the_algorithm_digest_length() {
let auth = OspfCryptoAuth::with_algorithm(
OspfCryptoAlgorithm::HmacSha256,
9,
0x00ab_cdef,
b"key".to_vec(),
);
assert_eq!(
auth.structured_auth_field(),
[0x00, 0x00, 0x09, 32, 0x00, 0xab, 0xcd, 0xef]
);
}
#[test]
fn ospf_crypto_auth_digest_lengths_match_each_algorithm() {
for (algorithm, expected) in [
(OspfCryptoAlgorithm::KeyedMd5, 16usize),
(OspfCryptoAlgorithm::HmacSha1, 20),
(OspfCryptoAlgorithm::HmacSha256, 32),
(OspfCryptoAlgorithm::HmacSha384, 48),
(OspfCryptoAlgorithm::HmacSha512, 64),
] {
let auth = OspfCryptoAuth::with_algorithm(algorithm, 1, 0, b"secret".to_vec());
assert_eq!(usize::from(auth.digest_len()), expected);
assert_eq!(auth.digest(b"packet").len(), expected);
}
}
#[test]
fn ospf_crypto_auth_hmac_sha256_digest_matches_the_hmac_crate() {
let auth = OspfCryptoAuth::with_algorithm(
OspfCryptoAlgorithm::HmacSha256,
1,
0,
b"secret".to_vec(),
);
let digest = auth.digest(b"the ospf packet bytes");
let mut mac = <Hmac<Sha256> as Mac>::new_from_slice(b"secret").unwrap();
mac.update(b"the ospf packet bytes");
let expected = mac.finalize().into_bytes().to_vec();
assert_eq!(digest, expected);
assert_eq!(digest.len(), 32);
}
#[test]
fn ospf_crypto_auth_md5_digest_matches_the_rfc_1321_abc_vector() {
let bare = {
let mut hasher = Md5::new();
hasher.update(b"abc");
let out: [u8; 16] = hasher.finalize().into();
out
};
assert_eq!(
bare,
[
0x90, 0x01, 0x50, 0x98, 0x3c, 0xd2, 0x4f, 0xb0, 0xd6, 0x96, 0x3f, 0x7d, 0x28, 0xe1,
0x7f, 0x72
]
);
let auth = OspfCryptoAuth::new(1, 0, Vec::new());
let digest = auth.md5_digest(b"abc");
let expected = {
let mut hasher = Md5::new();
hasher.update(b"abc");
hasher.update([0u8; 16]);
let out: [u8; 16] = hasher.finalize().into();
out
};
assert_eq!(digest, expected);
}
#[test]
fn ospf_crypto_auth_md5_digest_pads_or_truncates_the_key_to_sixteen_octets() {
let short = OspfCryptoAuth::new(1, 0, b"key".to_vec());
let short_expected = {
let mut padded = [0u8; 16];
padded[..3].copy_from_slice(b"key");
let mut hasher = Md5::new();
hasher.update(b"packet");
hasher.update(padded);
let out: [u8; 16] = hasher.finalize().into();
out
};
assert_eq!(short.md5_digest(b"packet"), short_expected);
let long_key = vec![0xABu8; 20];
let long = OspfCryptoAuth::new(1, 0, long_key.clone());
let long_expected = {
let mut hasher = Md5::new();
hasher.update(b"packet");
hasher.update(&long_key[..16]);
let out: [u8; 16] = hasher.finalize().into();
out
};
assert_eq!(long.md5_digest(b"packet"), long_expected);
}
}