use sha2::{Digest, Sha256};
use crate::error::{MavlinkError, Result};
use crate::frame::{Frame, Header, IFLAG_SIGNED};
pub const KEY_LEN: usize = 32;
pub const DEFAULT_TIMESTAMP_WINDOW: u64 = 6_000_000;
pub const MAVLINK_EPOCH_OFFSET_SECS: u64 = 1_420_070_400;
const MAX_STREAMS: usize = 16;
fn signature_48(key: &[u8; KEY_LEN], signed_region: &[u8], link_and_timestamp: &[u8]) -> [u8; 6] {
let mut hasher = Sha256::new();
hasher.update(key);
hasher.update(signed_region);
hasher.update(link_and_timestamp);
let digest = hasher.finalize();
let mut out = [0u8; 6];
out.copy_from_slice(&digest[..6]);
out
}
pub fn timestamp_from_unix_micros(unix_micros: u64) -> u64 {
let epoch_micros = MAVLINK_EPOCH_OFFSET_SECS * 1_000_000;
unix_micros.saturating_sub(epoch_micros) / 10
}
#[derive(Clone)]
pub struct Signer {
key: [u8; KEY_LEN],
link_id: u8,
timestamp: u64,
}
impl Signer {
pub fn new(key: [u8; KEY_LEN], link_id: u8, timestamp: u64) -> Self {
Signer {
key,
link_id,
timestamp,
}
}
pub fn sign(
&mut self,
header: Header,
msgid: u32,
payload: &[u8],
crc_extra: u8,
) -> Result<Frame> {
let mut frame = Frame::assemble_v2(header, msgid, payload, crc_extra, IFLAG_SIGNED)?;
let timestamp = self.timestamp;
{
let block = frame.signature_mut();
block[0] = self.link_id;
block[1..7].copy_from_slice(×tamp.to_le_bytes()[..6]);
}
let mac = signature_48(
&self.key,
frame.signed_region(),
&frame.signature().expect("just assembled as signed")[..7],
);
frame.signature_mut()[7..13].copy_from_slice(&mac);
self.timestamp = self.timestamp.wrapping_add(1);
Ok(frame)
}
pub fn link_id(&self) -> u8 {
self.link_id
}
}
#[derive(Clone, Copy)]
struct Stream {
system_id: u8,
component_id: u8,
link_id: u8,
timestamp: u64,
used: bool,
}
#[derive(Clone)]
pub struct Verifier {
key: [u8; KEY_LEN],
window: u64,
newest: u64,
streams: [Stream; MAX_STREAMS],
}
impl Verifier {
pub fn new(key: [u8; KEY_LEN]) -> Self {
Verifier {
key,
window: DEFAULT_TIMESTAMP_WINDOW,
newest: 0,
streams: [Stream {
system_id: 0,
component_id: 0,
link_id: 0,
timestamp: 0,
used: false,
}; MAX_STREAMS],
}
}
pub fn with_window(mut self, window: u64) -> Self {
self.window = window;
self
}
pub fn verify(&mut self, frame: &Frame) -> Result<()> {
let block = frame.signature().ok_or(MavlinkError::Unsigned)?;
let expected = signature_48(&self.key, frame.signed_region(), &block[..7]);
if expected != block[7..13] {
return Err(MavlinkError::BadSignature);
}
let link_id = block[0];
let mut timestamp_bytes = [0u8; 8];
timestamp_bytes[..6].copy_from_slice(&block[1..7]);
let timestamp = u64::from_le_bytes(timestamp_bytes);
let system_id = frame.system_id();
let component_id = frame.component_id();
match self.find_stream(system_id, component_id, link_id) {
Some(index) => {
if timestamp <= self.streams[index].timestamp {
return Err(MavlinkError::ReplayedTimestamp);
}
self.streams[index].timestamp = timestamp;
}
None => {
if timestamp + self.window < self.newest {
return Err(MavlinkError::ReplayedTimestamp);
}
self.remember(system_id, component_id, link_id, timestamp);
}
}
if timestamp > self.newest {
self.newest = timestamp;
}
Ok(())
}
fn find_stream(&self, system_id: u8, component_id: u8, link_id: u8) -> Option<usize> {
self.streams.iter().position(|stream| {
stream.used
&& stream.system_id == system_id
&& stream.component_id == component_id
&& stream.link_id == link_id
})
}
fn remember(&mut self, system_id: u8, component_id: u8, link_id: u8, timestamp: u64) {
let slot = self
.streams
.iter()
.position(|stream| !stream.used)
.unwrap_or_else(|| {
self.streams
.iter()
.enumerate()
.min_by_key(|(_, stream)| stream.timestamp)
.map(|(index, _)| index)
.unwrap_or(0)
});
self.streams[slot] = Stream {
system_id,
component_id,
link_id,
timestamp,
used: true,
};
}
}
#[cfg(test)]
mod tests {
use super::*;
const KEY: [u8; KEY_LEN] = [0x42; KEY_LEN];
fn signed_heartbeat(signer: &mut Signer, timestamp_seq: u8) -> Frame {
let header = Header::new(1, 1, timestamp_seq);
signer
.sign(header, 0, &[0, 0, 0, 0, 6, 8, 0, 3, 3], 50)
.unwrap()
}
#[test]
fn sha256_primitive_matches_the_nist_vector() {
let mut hasher = Sha256::new();
hasher.update(b"abc");
let digest = hasher.finalize();
let expected = [
0xba, 0x78, 0x16, 0xbf, 0x8f, 0x01, 0xcf, 0xea, 0x41, 0x41, 0x40, 0xde, 0x5d, 0xae,
0x22, 0x23, 0xb0, 0x03, 0x61, 0xa3, 0x96, 0x17, 0x7a, 0x9c, 0xb4, 0x10, 0xff, 0x61,
0xf2, 0x00, 0x15, 0xad,
];
assert_eq!(digest[..], expected[..]);
}
#[test]
fn the_signature_block_is_laid_out_as_the_spec_requires() {
let mut signer = Signer::new(KEY, 0x07, 0x0000_1122_3344_5566);
let frame = signed_heartbeat(&mut signer, 0);
let block = frame.signature().expect("signed");
assert_eq!(block[0], 0x07); assert_eq!(&block[1..7], &[0x66, 0x55, 0x44, 0x33, 0x22, 0x11]);
assert!(frame.is_signed());
}
#[test]
fn a_signed_frame_verifies() {
let mut signer = Signer::new(KEY, 1, 1_000_000);
let mut verifier = Verifier::new(KEY);
let frame = signed_heartbeat(&mut signer, 0);
assert!(verifier.verify(&frame).is_ok());
}
#[test]
fn a_tampered_frame_fails_verification() {
let mut signer = Signer::new(KEY, 1, 1_000_000);
let mut verifier = Verifier::new(KEY);
let frame = signed_heartbeat(&mut signer, 0);
let mut bytes = frame.as_bytes().to_vec();
let last = bytes.len() - 1;
bytes[last] ^= 0xFF;
let tampered = Frame::parse(&bytes, 50).unwrap();
assert_eq!(verifier.verify(&tampered), Err(MavlinkError::BadSignature));
}
#[test]
fn the_wrong_key_fails_verification() {
let mut signer = Signer::new(KEY, 1, 1_000_000);
let mut verifier = Verifier::new([0x99; KEY_LEN]);
let frame = signed_heartbeat(&mut signer, 0);
assert_eq!(verifier.verify(&frame), Err(MavlinkError::BadSignature));
}
#[test]
fn an_unsigned_frame_is_rejected_by_a_verifier() {
let header = Header::new(1, 1, 0);
let frame = Frame::encode_v2(header, 0, &[0, 0, 0, 0, 6, 8, 0, 3, 3], 50).unwrap();
let mut verifier = Verifier::new(KEY);
assert_eq!(verifier.verify(&frame), Err(MavlinkError::Unsigned));
}
#[test]
fn a_replayed_frame_is_rejected() {
let mut signer = Signer::new(KEY, 1, 1_000_000);
let mut verifier = Verifier::new(KEY);
let frame = signed_heartbeat(&mut signer, 0);
assert!(verifier.verify(&frame).is_ok());
assert_eq!(
verifier.verify(&frame),
Err(MavlinkError::ReplayedTimestamp)
);
}
#[test]
fn timestamps_must_increase_on_a_stream() {
let mut verifier = Verifier::new(KEY);
let mut newer = Signer::new(KEY, 1, 100);
let mut older = Signer::new(KEY, 1, 50);
let new_frame = signed_heartbeat(&mut newer, 0);
let old_frame = signed_heartbeat(&mut older, 1);
assert!(verifier.verify(&new_frame).is_ok());
assert_eq!(
verifier.verify(&old_frame),
Err(MavlinkError::ReplayedTimestamp)
);
}
#[test]
fn timestamp_conversion_uses_the_mavlink_epoch() {
assert_eq!(
timestamp_from_unix_micros(MAVLINK_EPOCH_OFFSET_SECS * 1_000_000),
0
);
assert_eq!(
timestamp_from_unix_micros((MAVLINK_EPOCH_OFFSET_SECS + 1) * 1_000_000),
100_000
);
}
}