1
0
Fork 0
mirror of https://github.com/sile/hls_m3u8.git synced 2024-11-25 08:31:00 +00:00

improve DecryptionKey

This commit is contained in:
Luro02 2020-03-25 11:49:16 +01:00
parent 7c26d2f7f1
commit fc1136265c
No known key found for this signature in database
GPG key ID: B66FD4F74501A9CF
5 changed files with 366 additions and 80 deletions

View file

@ -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 {

View file

@ -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<KeyFormat>,
/// 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());

View file

@ -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<u128> {
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<Option<[u8; 0x10]>> 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<Self, Self::Err> {
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::<InitializationVector>()
.is_err());
// too small:
assert!("0xFF".parse::<InitializationVector>().is_err());
// too large:
assert!("0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF"
.parse::<InitializationVector>()
.is_err());
}
}

View file

@ -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,
}

View file

@ -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<T: AsRef<str>>(s: T) -> crate::Result<bool> {
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());