From fc1136265c7a0de19c5996aafbf73f5d36012025 Mon Sep 17 00:00:00 2001 From: Luro02 <24826124+Luro02@users.noreply.github.com> Date: Wed, 25 Mar 2020 11:49:16 +0100 Subject: [PATCH] improve DecryptionKey --- src/tags/master_playlist/session_key.rs | 6 +- src/types/decryption_key.rs | 83 ++++--- src/types/initialization_vector.rs | 305 ++++++++++++++++++++++++ src/types/key_format.rs | 10 +- src/utils.rs | 42 ---- 5 files changed, 366 insertions(+), 80 deletions(-) create mode 100644 src/types/initialization_vector.rs diff --git a/src/tags/master_playlist/session_key.rs b/src/tags/master_playlist/session_key.rs index d3ecb8d..0522abe 100644 --- a/src/tags/master_playlist/session_key.rs +++ b/src/tags/master_playlist/session_key.rs @@ -14,14 +14,14 @@ use crate::{Error, RequiredVersion}; /// preload these keys without having to read the [`MediaPlaylist`]s /// first. /// -/// If an [`ExtXSessionKey`] is used, the values of [`ExtXKey::method`], -/// [`ExtXKey::key_format`] and [`ExtXKey::key_format_versions`] must match any +/// If an [`ExtXSessionKey`] is used, the values of [`DecryptionKey::method`], +/// [`DecryptionKey::format`] and [`DecryptionKey::versions`] must match any /// [`ExtXKey`] with the same uri field. /// /// [`MediaPlaylist`]: crate::MediaPlaylist /// [`MasterPlaylist`]: crate::MasterPlaylist +/// [`ExtXKey`]: crate::tags::ExtXKey #[derive(AsRef, AsMut, From, Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] -#[from(forward)] pub struct ExtXSessionKey(pub DecryptionKey); impl ExtXSessionKey { diff --git a/src/types/decryption_key.rs b/src/types/decryption_key.rs index 68396a3..0caf17a 100644 --- a/src/types/decryption_key.rs +++ b/src/types/decryption_key.rs @@ -5,53 +5,73 @@ use derive_builder::Builder; use shorthand::ShortHand; use crate::attribute::AttributePairs; -use crate::types::{EncryptionMethod, KeyFormat, KeyFormatVersions, ProtocolVersion}; -use crate::utils::{parse_iv_from_str, quote, unquote}; +use crate::types::{ + EncryptionMethod, InitializationVector, KeyFormat, KeyFormatVersions, ProtocolVersion, +}; +use crate::utils::{quote, unquote}; use crate::{Error, RequiredVersion}; +/// Specifies how to decrypt encrypted data from the server. #[derive(ShortHand, Builder, Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)] #[builder(setter(into), build_fn(validate = "Self::validate"))] -#[shorthand(enable(must_use, into))] +#[shorthand(enable(skip, must_use, into))] +#[non_exhaustive] pub struct DecryptionKey { - /// HLS supports multiple [`EncryptionMethod`]s for a [`MediaSegment`]. + /// The encryption method, which has been used to encrypt the data. /// - /// For example `AES-128`. + /// An [`EncryptionMethod::Aes128`] signals that the data is encrypted using + /// the Advanced Encryption Standard (AES) with a 128-bit key, Cipher Block + /// Chaining (CBC), and Public-Key Cryptography Standards #7 (PKCS7) + /// padding. CBC is restarted on each segment boundary, using either the + /// [`DecryptionKey::iv`] field or the [`MediaSegment::number`] as the IV. + /// + /// An [`EncryptionMethod::SampleAes`] means that the [`MediaSegment`]s + /// contain media samples, such as audio or video, that are encrypted using + /// the Advanced Encryption Standard (Aes128). How these media streams are + /// encrypted and encapsulated in a segment depends on the media encoding + /// and the media format of the segment. /// /// ## Note /// /// This field is required. /// + /// [`MediaSegment::number`]: crate::MediaSegment::number /// [`MediaSegment`]: crate::MediaSegment - //#[shorthand(enable(skip))] - #[shorthand(enable(copy))] pub method: EncryptionMethod, - /// An `URI` that specifies how to obtain the key. + /// This uri points to a key file, which contains the cipher key. /// /// ## Note /// - /// This attribute is required, if the [`EncryptionMethod`] is not `None`. + /// This field is required. #[builder(setter(into, strip_option), default)] + #[shorthand(disable(skip))] pub(crate) uri: String, - /// An IV (initialization vector) is used to prevent repetitions between - /// segments of encrypted data. + /// An initialization vector (IV) is a fixed size input that can be used + /// along with a secret key for data encryption. + /// + /// ## Note + /// + /// This field is optional and an absent value indicates that + /// [`MediaSegment::number`] should be used instead. + /// + /// [`MediaSegment::number`]: crate::MediaSegment::number + #[builder(setter(into, strip_option), default)] + pub iv: InitializationVector, + /// A server may offer multiple ways to retrieve a key by providing multiple + /// [`DecryptionKey`]s with different [`KeyFormat`] values. + /// + /// An [`EncryptionMethod::Aes128`] uses 16-octet (16 byte/128 bit) keys. If + /// the format is [`KeyFormat::Identity`], the key file is a single packed + /// array of 16 octets (16 byte/128 bit) in binary format. /// /// ## Note /// /// This field is optional. #[builder(setter(into, strip_option), default)] - // TODO: workaround for https://github.com/Luro02/shorthand/issues/20 - #[shorthand(enable(copy), disable(option_as_ref))] - pub(crate) iv: Option<[u8; 0x10]>, - /// The [`KeyFormat`] specifies how the key is - /// represented in the resource identified by the `URI`. - /// - /// ## Note - /// - /// This field is optional. - #[builder(setter(into, strip_option), default)] - #[shorthand(enable(copy))] pub format: Option, - /// The [`KeyFormatVersions`] attribute. + /// A list of numbers that can be used to indicate which version(s) + /// this instance complies with, if more than one version of a particular + /// [`KeyFormat`] is defined. /// /// ## Note /// @@ -67,7 +87,7 @@ impl DecryptionKey { Self { method, uri: uri.into(), - iv: None, + iv: InitializationVector::default(), format: None, versions: None, } @@ -115,7 +135,7 @@ impl FromStr for DecryptionKey { uri = Some(unquoted_uri); } } - "IV" => iv = Some(parse_iv_from_str(value)?), + "IV" => iv = Some(value.parse()?), "KEYFORMAT" => format = Some(value.parse()?), "KEYFORMATVERSIONS" => versions = Some(value.parse()?), _ => { @@ -128,6 +148,7 @@ impl FromStr for DecryptionKey { let method = method.ok_or_else(|| Error::missing_value("METHOD"))?; let uri = uri.ok_or_else(|| Error::missing_value("URI"))?; + let iv = iv.unwrap_or_default(); Ok(Self { method, @@ -143,11 +164,8 @@ impl fmt::Display for DecryptionKey { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!(f, "METHOD={},URI={}", self.method, quote(&self.uri))?; - if let Some(value) = &self.iv { - let mut result = [0; 0x10 * 2]; - ::hex::encode_to_slice(value, &mut result).unwrap(); - - write!(f, ",IV=0x{}", ::core::str::from_utf8(&result).unwrap())?; + if let InitializationVector::Aes128(_) = &self.iv { + write!(f, ",IV={}", &self.iv)?; } if let Some(value) = &self.format { @@ -215,9 +233,10 @@ mod test { #[test] fn test_builder() { let mut key = DecryptionKey::new(EncryptionMethod::Aes128, "https://www.example.com/"); - key.set_iv(Some([ + key.iv = [ 16, 239, 143, 117, 140, 165, 85, 17, 85, 132, 187, 91, 60, 104, 127, 82, - ])); + ] + .into(); key.format = Some(KeyFormat::Identity); key.versions = Some(vec![1, 2, 3, 4, 5].into()); diff --git a/src/types/initialization_vector.rs b/src/types/initialization_vector.rs new file mode 100644 index 0000000..0dcb9be --- /dev/null +++ b/src/types/initialization_vector.rs @@ -0,0 +1,305 @@ +use core::fmt; +use core::str::FromStr; + +use crate::Error; + +/// An initialization vector (IV) is a fixed size input that can be used along +/// with a secret key for data encryption. +/// +/// The use of an IV prevents repetition in encrypted data, making it more +/// difficult for a hacker using a dictionary attack to find patterns and break +/// a cipher. For example, a sequence might appear twice or more within the body +/// of a message. If there are repeated sequences in encrypted data, an attacker +/// could assume that the corresponding sequences in the message were also +/// identical. The IV prevents the appearance of corresponding duplicate +/// character sequences in the ciphertext. +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] +#[non_exhaustive] +pub enum InitializationVector { + /// An IV for use with Aes128. + Aes128([u8; 0x10]), + /// An [`ExtXKey`] tag with [`KeyFormat::Identity`] that does not have an IV + /// field indicates that the [`MediaSegment::number`] is to be used as the + /// IV when decrypting a `MediaSegment`. + /// + /// [`ExtXKey`]: crate::tags::ExtXKey + /// [`KeyFormat::Identity`]: crate::types::KeyFormat::Identity + /// [`MediaSegment::number`]: crate::MediaSegment::number + Number(u128), + /// Signals that an IV is missing. + Missing, +} + +impl InitializationVector { + /// Returns the IV as an [`u128`]. `None` is returned for + /// [`InitializationVector::Missing`]. + /// + /// # Example + /// + /// ``` + /// # use hls_m3u8::types::InitializationVector; + /// assert_eq!( + /// InitializationVector::Aes128([ + /// 0x12, 0x34, 0x56, 0x78, 0x90, 0x12, 0x34, 0x56, 0x78, 0x90, 0x12, 0x34, 0x56, 0x78, + /// 0x90, 0x12 + /// ]) + /// .to_u128(), + /// Some(0x12345678901234567890123456789012) + /// ); + /// + /// assert_eq!(InitializationVector::Number(0x10).to_u128(), Some(0x10)); + /// + /// assert_eq!(InitializationVector::Missing.to_u128(), None); + /// ``` + #[must_use] + pub fn to_u128(&self) -> Option { + match *self { + Self::Aes128(v) => Some(u128::from_be_bytes(v)), + Self::Number(n) => Some(n), + Self::Missing => None, + } + } + + /// Returns the IV as a slice, which can be used to for example decrypt + /// a [`MediaSegment`]. `None` is returned for + /// [`InitializationVector::Missing`]. + /// + /// # Example + /// + /// ``` + /// # use hls_m3u8::types::InitializationVector; + /// assert_eq!( + /// InitializationVector::Aes128([ + /// 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, + /// 0x0F, 0x10, + /// ]) + /// .to_slice(), + /// Some([ + /// 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, + /// 0x0F, 0x10, + /// ]) + /// ); + /// + /// assert_eq!( + /// InitializationVector::Number(0x12345678901234567890123456789012).to_slice(), + /// Some([ + /// 0x12, 0x34, 0x56, 0x78, 0x90, 0x12, 0x34, 0x56, 0x78, 0x90, 0x12, 0x34, 0x56, 0x78, + /// 0x90, 0x12 + /// ]) + /// ); + /// + /// assert_eq!(InitializationVector::Missing.to_slice(), None); + /// ``` + /// + /// [`MediaSegment`]: crate::MediaSegment + #[must_use] + pub fn to_slice(&self) -> Option<[u8; 0x10]> { + match &self { + Self::Aes128(v) => Some(*v), + Self::Number(v) => Some(v.to_be_bytes()), + Self::Missing => None, + } + } + + /// Returns `true` if the initialization vector is not missing. + /// + /// # Example + /// + /// ``` + /// # use hls_m3u8::types::InitializationVector; + /// assert_eq!( + /// InitializationVector::Aes128([ + /// 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, + /// 0x0F, 0x10, + /// ]) + /// .is_some(), + /// true + /// ); + /// + /// assert_eq!(InitializationVector::Number(4).is_some(), true); + /// + /// assert_eq!(InitializationVector::Missing.is_some(), false); + /// ``` + #[must_use] + #[inline] + pub fn is_some(&self) -> bool { *self != Self::Missing } + + /// Returns `true` if the initialization vector is missing. + /// + /// # Example + /// + /// ``` + /// # use hls_m3u8::types::InitializationVector; + /// assert_eq!( + /// InitializationVector::Aes128([ + /// 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, + /// 0x0F, 0x10, + /// ]) + /// .is_none(), + /// false + /// ); + /// + /// assert_eq!(InitializationVector::Number(4).is_none(), false); + /// + /// assert_eq!(InitializationVector::Missing.is_none(), true); + /// ``` + #[must_use] + #[inline] + pub fn is_none(&self) -> bool { *self == Self::Missing } +} + +impl Default for InitializationVector { + fn default() -> Self { Self::Missing } +} + +impl From<[u8; 0x10]> for InitializationVector { + fn from(value: [u8; 0x10]) -> Self { Self::Aes128(value) } +} + +impl From> for InitializationVector { + fn from(value: Option<[u8; 0x10]>) -> Self { + match value { + Some(v) => Self::Aes128(v), + None => Self::Missing, + } + } +} + +impl fmt::Display for InitializationVector { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match &self { + Self::Aes128(buffer) => { + let mut result = [0; 0x10 * 2]; + ::hex::encode_to_slice(buffer, &mut result).unwrap(); + + write!(f, "0x{}", ::core::str::from_utf8(&result).unwrap())?; + } + Self::Number(num) => { + write!(f, "InitializationVector::Number({})", num)?; + } + Self::Missing => { + write!(f, "InitializationVector::Missing")?; + } + } + + Ok(()) + } +} + +impl FromStr for InitializationVector { + type Err = Error; + + fn from_str(input: &str) -> Result { + if !(input.starts_with("0x") || input.starts_with("0X")) { + return Err(Error::custom("An IV should either start with `0x` or `0X`")); + } + + if input.len() - 2 != 32 { + return Err(Error::custom( + "An IV must be 32 bytes long + 2 bytes for 0x/0X", + )); + } + + let mut result = [0; 16]; + + ::hex::decode_to_slice(&input.as_bytes()[2..], &mut result).map_err(Error::hex)?; + + Ok(Self::Aes128(result)) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use pretty_assertions::assert_eq; + + #[test] + fn test_default() { + assert_eq!( + InitializationVector::default(), + InitializationVector::Missing + ); + } + + #[test] + fn test_from() { + assert_eq!( + InitializationVector::from([ + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF + ]), + InitializationVector::Aes128([ + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF + ]) + ); + + assert_eq!( + InitializationVector::from(None), + InitializationVector::Missing + ); + + assert_eq!( + InitializationVector::from(Some([ + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF + ])), + InitializationVector::Aes128([ + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF + ]) + ) + } + + #[test] + fn test_display() { + assert_eq!( + InitializationVector::Aes128([ + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF + ]) + .to_string(), + "0xffffffffffffffffffffffffffffffff".to_string() + ); + + assert_eq!( + InitializationVector::Number(5).to_string(), + "InitializationVector::Number(5)".to_string() + ); + + assert_eq!( + InitializationVector::Missing.to_string(), + "InitializationVector::Missing".to_string() + ); + } + + #[test] + fn test_parser() { + assert_eq!( + InitializationVector::Aes128([ + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF + ]), + "0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF".parse().unwrap() + ); + + assert_eq!( + InitializationVector::Aes128([ + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF + ]), + "0XFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF".parse().unwrap() + ); + + // missing `0x` at the start: + assert!("0FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF" + .parse::() + .is_err()); + // too small: + assert!("0xFF".parse::().is_err()); + // too large: + assert!("0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF" + .parse::() + .is_err()); + } +} diff --git a/src/types/key_format.rs b/src/types/key_format.rs index f3b995a..77d89b6 100644 --- a/src/types/key_format.rs +++ b/src/types/key_format.rs @@ -5,12 +5,16 @@ use crate::types::ProtocolVersion; use crate::utils::{quote, tag, unquote}; use crate::{Error, RequiredVersion}; -/// [`KeyFormat`] specifies, how the key is represented in the -/// resource identified by the `URI`. +/// Specifies how the key is represented in the resource identified by the +/// `URI`. #[non_exhaustive] #[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug)] pub enum KeyFormat { - /// The key is a single packed array of 16 octets in binary format. + /// An [`EncryptionMethod::Aes128`] uses 16-octet (16 byte/128 bit) keys. If + /// the format is [`KeyFormat::Identity`], the key file is a single packed + /// array of 16 octets (16 byte/128 bit) in binary format. + /// + /// [`EncryptionMethod::Aes128`]: crate::types::EncryptionMethod::Aes128 Identity, } diff --git a/src/utils.rs b/src/utils.rs index 7ff9ddd..b210675 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -12,22 +12,6 @@ macro_rules! required_version { } } -pub(crate) fn parse_iv_from_str(input: &str) -> crate::Result<[u8; 16]> { - if !(input.starts_with("0x") || input.starts_with("0X")) { - return Err(Error::invalid_input()); - } - - if input.len() - 2 != 32 { - return Err(Error::invalid_input()); - } - - let mut result = [0; 16]; - - hex::decode_to_slice(&input.as_bytes()[2..], &mut result).map_err(Error::hex)?; - - Ok(result) -} - pub(crate) fn parse_yes_or_no>(s: T) -> crate::Result { match s.as_ref() { "YES" => Ok(true), @@ -86,32 +70,6 @@ mod tests { use super::*; use pretty_assertions::assert_eq; - #[test] - fn test_parse_iv_from_str() { - assert_eq!( - parse_iv_from_str("0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF").unwrap(), - [ - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF - ] - ); - - assert_eq!( - parse_iv_from_str("0XFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF").unwrap(), - [ - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF - ] - ); - - // missing `0x` at the start: - assert!(parse_iv_from_str("0FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF").is_err()); - // too small: - assert!(parse_iv_from_str("0xFF").is_err()); - // too large: - assert!(parse_iv_from_str("0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF").is_err()); - } - #[test] fn test_parse_yes_or_no() { assert!(parse_yes_or_no("YES").unwrap());