#[cfg(test)]
mod integrity_test;
use crate::attributes::*;
use crate::message::*;
use crypto::{CryptoError, HashAlgorithm, HmacAlgorithm, RTCCrypto, SecretVec};
use shared::error::*;
use std::fmt;
pub(crate) const CREDENTIALS_SEP: &str = ":";
#[derive(Clone)]
pub struct MessageIntegrity<'a> {
key: SecretVec,
crypto: &'a dyn RTCCrypto,
}
fn crypto_error(error: CryptoError) -> Error {
Error::Crypto(error.to_string())
}
impl<'a> fmt::Display for MessageIntegrity<'a> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
f,
"MESSAGE-INTEGRITY key: [REDACTED; {} bytes]",
self.key.len()
)
}
}
impl<'a> Setter for MessageIntegrity<'a> {
fn add_to(&self, m: &mut Message) -> Result<()> {
for a in &m.attributes.0 {
if a.typ == ATTR_FINGERPRINT {
return Err(Error::ErrFingerprintBeforeIntegrity);
}
}
let length = m.length;
m.length += (MESSAGE_INTEGRITY_SIZE + ATTRIBUTE_HEADER_SIZE) as u32;
m.write_length(); let mut value = [0_u8; MESSAGE_INTEGRITY_SIZE];
let result = self
.crypto
.new_hmac(HmacAlgorithm::Sha1, self.key.as_ref())
.and_then(|mut mac| mac.sign(&[&m.raw], &mut value));
m.length = length; m.write_length();
result.map_err(crypto_error)?;
m.add(ATTR_MESSAGE_INTEGRITY, &value);
Ok(())
}
}
pub(crate) const MESSAGE_INTEGRITY_SIZE: usize = 20;
impl<'a> MessageIntegrity<'a> {
#[must_use]
pub fn new_raw_integrity_with_provider(
key: impl Into<Vec<u8>>,
crypto: &'a dyn RTCCrypto,
) -> Self {
Self {
key: SecretVec::new(key.into()),
crypto,
}
}
#[must_use]
pub fn new_short_term_integrity_with_provider(
password: String,
crypto: &'a dyn RTCCrypto,
) -> Self {
Self::new_raw_integrity_with_provider(password.into_bytes(), crypto)
}
pub fn new_long_term_integrity_with_provider(
username: String,
realm: String,
password: String,
crypto: &'a dyn RTCCrypto,
) -> Result<Self> {
let key = MessageIntegrity::long_term_integrity_key(username, realm, password, crypto)?;
Ok(Self::new_raw_integrity_with_provider(key, crypto))
}
pub fn long_term_integrity_key(
username: String,
realm: String,
password: String,
crypto: &'a dyn RTCCrypto,
) -> Result<Vec<u8>> {
let credentials = [username, realm, password].join(CREDENTIALS_SEP);
let key = crypto
.hash(HashAlgorithm::Md5, credentials.as_bytes())
.map_err(crypto_error)?;
if key.len() != 16 {
return Err(Error::Crypto(format!(
"provider returned an invalid MD5 digest length: {}",
key.len()
)));
}
Ok(key)
}
pub fn check(m: &mut Message, key: &[u8], crypto: &dyn RTCCrypto) -> Result<()> {
let v = m.get(ATTR_MESSAGE_INTEGRITY)?;
let length = m.length as usize;
let mut after_integrity = false;
let mut size_reduced = 0;
for a in &m.attributes.0 {
if after_integrity {
size_reduced += nearest_padded_value_length(a.length as usize);
size_reduced += ATTRIBUTE_HEADER_SIZE;
}
if a.typ == ATTR_MESSAGE_INTEGRITY {
after_integrity = true;
}
}
m.length -= size_reduced as u32;
m.write_length();
let start_of_hmac = MESSAGE_HEADER_SIZE + m.length as usize
- (ATTRIBUTE_HEADER_SIZE + MESSAGE_INTEGRITY_SIZE);
let b = &m.raw[..start_of_hmac]; let result = crypto
.new_hmac(HmacAlgorithm::Sha1, key)
.and_then(|mut mac| mac.verify(&[b], &v));
m.length = length as u32;
m.write_length(); match result {
Ok(()) => Ok(()),
Err(CryptoError::AuthenticationFailed | CryptoError::InvalidTagLength { .. }) => {
Err(Error::ErrIntegrityMismatch)
}
Err(error) => Err(crypto_error(error)),
}
}
}