use std::borrow::Cow;
use std::fmt;
use std::io::prelude::*;
use std::str;
use curve25519_dalek::constants;
use curve25519_dalek::edwards::EdwardsPoint;
use curve25519_dalek::scalar::Scalar;
use num_traits::{FromPrimitive as _, ToPrimitive as _};
use omnom::{ReadExt, WriteExt};
use sha2::{Digest, Sha512};
use thiserror::Error;
use time::OffsetDateTime;
use tsproto_types::crypto::{EccKeyPrivEd25519, EccKeyPubEd25519};
use tsproto_types::LicenseType;
pub const TIMESTAMP_OFFSET: i64 = 0x50e2_2700;
type Result<T> = std::result::Result<T, Error>;
#[derive(Error, Debug)]
#[non_exhaustive]
pub enum Error {
#[error(
"License must not exceed bounds {outer_start} - {outer_end} but has {inner_start} - \
{inner_end}"
)]
Bounds {
outer_start: OffsetDateTime,
outer_end: OffsetDateTime,
inner_start: OffsetDateTime,
inner_end: OffsetDateTime,
},
#[error("Failed to deserialize license: {0}")]
Deserialize(#[source] std::io::Error),
#[error("Failed to deserialize license: {0}")]
DeserializeString(#[source] std::str::Utf8Error),
#[error("License is only valid between {start} and {end}")]
Expired { start: OffsetDateTime, end: OffsetDateTime },
#[error("Cannot uncompress license public key")]
InvalidPublicKey,
#[error("Invalid data {0:#x} in intermediate license")]
IntermediateInvalidData(u32),
#[error("Invalid public root key for license")]
InvalidRootKey,
#[error("Non-null-terminated string")]
NonterminatedString,
#[error("License contains no private key")]
NoPrivateKey,
#[error("Failed to serialize license: {0}")]
Serialize(#[source] std::io::Error),
#[error("Too many license blocks: {0}")]
TooManyBlocks(usize),
#[error("License too short")]
TooShort,
#[error("Unknown license block type {0}")]
UnknownBlockType(u8),
#[error("Unknown license type {0}")]
UnknownLicenseType(u8),
#[error("Unsupported license version {0}")]
UnsupportedVersion(u8),
#[error("Wrong key kind {0} in license")]
WrongKeyKind(u8),
}
#[derive(Debug, Clone)]
pub enum LicenseKey {
Public(EccKeyPubEd25519),
Private(EccKeyPrivEd25519),
}
#[derive(Clone)]
pub struct License {
pub key: LicenseKey,
pub not_valid_before: OffsetDateTime,
pub not_valid_after: OffsetDateTime,
pub hash: [u8; 32],
pub inner: InnerLicense,
}
#[derive(Debug, Clone)]
pub enum InnerLicense {
Intermediate {
issuer: String,
data: u8,
}, Website {
issuer: String,
}, Server {
issuer: String,
license_type: LicenseType,
data: u32,
}, Code {
issuer: String,
}, Ephemeral, }
#[derive(Clone, Debug, Default)]
pub struct Licenses {
pub blocks: Vec<License>,
}
impl LicenseKey {
pub fn get_pub_bytes(&self) -> Cow<[u8; 32]> {
match *self {
LicenseKey::Public(ref k) => Cow::Borrowed(&(k.0).0),
LicenseKey::Private(ref k) => Cow::Owned((k.to_pub().0).0),
}
}
pub fn get_pub(&self) -> Result<EdwardsPoint> {
match *self {
LicenseKey::Public(ref k) => k.0.decompress().ok_or(Error::InvalidPublicKey),
LicenseKey::Private(ref k) => Ok(&constants::ED25519_BASEPOINT_TABLE * &k.0),
}
}
}
impl InnerLicense {
pub fn type_id(&self) -> u8 {
match *self {
InnerLicense::Intermediate { .. } => 0,
InnerLicense::Website { .. } => 1,
InnerLicense::Server { .. } => 2,
InnerLicense::Code { .. } => 3,
InnerLicense::Ephemeral { .. } => 32,
}
}
}
impl Licenses {
pub fn new() -> Self { Self::default() }
pub fn parse_ignore_expired(data: &[u8]) -> Result<Self> { Self::parse_internal(data, false) }
pub fn parse(data: &[u8]) -> Result<Self> { Self::parse_internal(data, true) }
pub fn parse_internal(mut data: &[u8], check_expired: bool) -> Result<Self> {
let version = data[0];
if version != 0 && version != 1 {
return Err(Error::UnsupportedVersion(version));
}
let mut res = Licenses { blocks: Vec::new() };
data = &data[1..];
let now = OffsetDateTime::now_utc();
let mut bounds = None;
while !data.is_empty() {
if res.blocks.len() >= 8 {
return Err(Error::TooManyBlocks(res.blocks.len()));
}
let (license, len) = License::parse(data)?;
if license.not_valid_before > now && check_expired {
return Err(Error::Expired {
start: license.not_valid_before,
end: license.not_valid_after,
});
}
if license.not_valid_after < now && check_expired {
return Err(Error::Expired {
start: license.not_valid_before,
end: license.not_valid_after,
});
}
if let Some((start, end)) = bounds {
if license.not_valid_before < start || license.not_valid_after > end {
return Err(Error::Bounds {
outer_start: start,
outer_end: end,
inner_start: license.not_valid_before,
inner_end: license.not_valid_after,
});
}
}
bounds = Some((license.not_valid_before, license.not_valid_after));
res.blocks.push(license);
data = &data[len..];
}
Ok(res)
}
pub fn write(&self, mut w: &mut dyn Write) -> Result<()> {
w.write_be(1u8).map_err(Error::Serialize)?;
for l in &self.blocks {
l.write(w)?;
}
Ok(())
}
pub fn derive_public_key(&self, root: EccKeyPubEd25519) -> Result<EdwardsPoint> {
let mut last_round = root.0.decompress().ok_or(Error::InvalidRootKey)?;
for l in &self.blocks {
last_round = l.derive_public_key(&last_round)?;
}
Ok(last_round)
}
pub fn derive_private_key(
&self, starting_block: usize, first_key: EccKeyPrivEd25519,
) -> Result<EccKeyPrivEd25519> {
let mut res = first_key;
for l in &self.blocks[starting_block..] {
res = l.derive_private_key(&res)?;
}
Ok(res)
}
}
impl License {
pub fn fill_hash(&mut self) {
let mut data = Vec::new();
self.write(&mut data).unwrap();
let hash_out = Sha512::digest(&data[1..]);
let mut hash = [0; 32];
hash.copy_from_slice(&hash_out.as_slice()[..32]);
self.hash = hash;
}
pub fn parse(data: &[u8]) -> Result<(Self, usize)> {
const MIN_LEN: usize = 42;
if data.len() < MIN_LEN {
return Err(Error::TooShort);
}
if data[0] != 0 {
return Err(Error::WrongKeyKind(data[0]));
}
let mut key_data = [0; 32];
key_data.copy_from_slice(&data[1..33]);
let before_ts: u32 = (&data[34..]).read_be().map_err(Error::Deserialize)?;
let after_ts: u32 = (&data[38..]).read_be().map_err(Error::Deserialize)?;
let (inner, extra_len) = match data[33] {
0 => {
let license_data: u32 = (&data[42..]).read_be().map_err(Error::Deserialize)?;
if license_data > 0x7f {
return Err(Error::IntermediateInvalidData(license_data));
}
let len = if let Some(len) = data[46..].iter().position(|&b| b == 0) {
len
} else {
return Err(Error::NonterminatedString);
};
let issuer = str::from_utf8(&data[46..46 + len])
.map_err(Error::DeserializeString)?
.to_string();
(InnerLicense::Intermediate { issuer, data: license_data as u8 }, 5 + len)
}
2 => {
if data.len() < 47 {
return Err(Error::TooShort);
}
let license_type =
LicenseType::from_u8(data[42]).ok_or(Error::UnknownLicenseType(data[42]))?;
let license_data: u32 = (&data[43..]).read_be().map_err(Error::Deserialize)?;
let len = if let Some(len) = data[47..].iter().position(|&b| b == 0) {
len
} else {
return Err(Error::NonterminatedString);
};
if data.len() < 47 + len {
return Err(Error::TooShort);
}
let issuer = str::from_utf8(&data[47..47 + len])
.map_err(Error::DeserializeString)?
.to_string();
(InnerLicense::Server { issuer, license_type, data: license_data }, 6 + len)
}
32 => (InnerLicense::Ephemeral, 0),
i => {
return Err(Error::UnknownBlockType(i));
}
};
let all_len = MIN_LEN + extra_len;
let hash_out = Sha512::digest(&data[1..all_len]);
let mut hash = [0; 32];
hash.copy_from_slice(&hash_out.as_slice()[..32]);
Ok((
License {
key: LicenseKey::Public(EccKeyPubEd25519::from_bytes(key_data)),
not_valid_before: OffsetDateTime::from_unix_timestamp(
i64::from(before_ts) + TIMESTAMP_OFFSET,
),
not_valid_after: OffsetDateTime::from_unix_timestamp(
i64::from(after_ts) + TIMESTAMP_OFFSET,
),
hash,
inner,
},
all_len,
))
}
pub fn write(&self, mut w: &mut dyn Write) -> Result<()> {
w.write_be(0u8).map_err(Error::Serialize)?;
w.write_all(self.key.get_pub_bytes().as_ref()).map_err(Error::Serialize)?;
w.write_be(self.inner.type_id()).map_err(Error::Serialize)?;
w.write_be((self.not_valid_before.unix_timestamp() - TIMESTAMP_OFFSET) as u32)
.map_err(Error::Serialize)?;
w.write_be((self.not_valid_after.unix_timestamp() - TIMESTAMP_OFFSET) as u32)
.map_err(Error::Serialize)?;
match self.inner {
InnerLicense::Intermediate { ref issuer, data } => {
w.write_be(u32::from(data)).map_err(Error::Serialize)?;
w.write_all(issuer.as_bytes()).map_err(Error::Serialize)?;
w.write_be(0u8).map_err(Error::Serialize)?;
}
InnerLicense::Website { ref issuer } => {
w.write_all(issuer.as_bytes()).map_err(Error::Serialize)?;
w.write_be(0u8).map_err(Error::Serialize)?;
}
InnerLicense::Server { ref issuer, license_type, data } => {
w.write_be(license_type.to_u8().unwrap()).map_err(Error::Serialize)?;
w.write_be(data).map_err(Error::Serialize)?;
w.write_all(issuer.as_bytes()).map_err(Error::Serialize)?;
w.write_be(0u8).map_err(Error::Serialize)?;
}
InnerLicense::Code { ref issuer } => {
w.write_all(issuer.as_bytes()).map_err(Error::Serialize)?;
w.write_be(0u8).map_err(Error::Serialize)?;
}
InnerLicense::Ephemeral => {}
}
Ok(())
}
pub fn get_hash_key(&self) -> Scalar {
let mut hash_key = self.hash;
hash_key[0] &= 248;
hash_key[31] &= 63;
hash_key[31] |= 64;
Scalar::from_bytes_mod_order(hash_key)
}
pub fn derive_public_key(&self, parent_key: &EdwardsPoint) -> Result<EdwardsPoint> {
let hash_key = self.get_hash_key();
let pub_key = self.key.get_pub()?;
Ok(pub_key * hash_key + parent_key)
}
pub fn derive_private_key(&self, parent_key: &EccKeyPrivEd25519) -> Result<EccKeyPrivEd25519> {
let priv_key = if let LicenseKey::Private(ref k) = self.key {
&k.0
} else {
return Err(Error::NoPrivateKey);
};
let hash_key = self.get_hash_key();
Ok(EccKeyPrivEd25519(priv_key * hash_key + parent_key.0))
}
}
impl fmt::Debug for License {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "License {{ key: ")?;
match &self.key {
LicenseKey::Public(k) => write!(f, "{:?}", k)?,
LicenseKey::Private(k) => write!(f, "{:?}", k)?,
}
let from = self.not_valid_before.format("%F %T");
let to = self.not_valid_after.format("%F %T");
write!(f, ", valid_between: {} - {}, ", from, to)?;
write!(f, "inner: {:?} }}", self.inner)?;
Ok(())
}
}
#[cfg(test)]
mod tests {
use super::*;
use base64;
#[test]
fn parse_standard_license() {
Licenses::parse_ignore_expired(&base64::decode("AQA1hUFJiiSs0wFXkYuPUJVcDa6XCrZTcsvkB0\
Ffzz4CmwIITRXgCqeTYAcAAAAgQW5vbnltb3VzAACiIBip9hQaK6P3QhwOJs/BkPn0i\
oyIDPaNgzJ6M8x0kiAJf4hxCYAxMQ==").unwrap()).unwrap();
}
#[test]
fn parse_standard_license_expired() {
assert!(Licenses::parse(&base64::decode("AQA1hUFJiiSs0wFXkYuPUJVcDa6XCrZTcsvkB0\
Ffzz4CmwIITRXgCqeTYAcAAAAgQW5vbnltb3VzAACiIBip9hQaK6P3QhwOJs/BkPn0i\
oyIDPaNgzJ6M8x0kiAJf4hxCYAxMQ==").unwrap()).is_err());
}
#[test]
fn parse_aal_license() {
Licenses::parse_ignore_expired(&base64::decode("AQCvbHFTQDY/terPeilrp/ECU9xCH5U3xC92lY\
TNaY/0KQAJFueAazbsgAAAACVUZWFtU3BlYWsgU3lzdGVtcyBHbWJIAABhl9gwla/UJ\
p2Eszst9TRVXO/PeE6a6d+CTI6Pg7OEVgAJc5CrL4Nh8gAAACRUZWFtU3BlYWsgc3lz\
dGVtcyBHbWJIAACvTQIgpv6zmLZq3znh7ygmOSokGFkFjz4bTigrOnetrgIJdIIACdS\
/gAYAAAAAU29zc2VuU3lzdGVtcy5iaWQAADY7+uV1CQ1niOvYSdGzsu83kPTNWijovr\
3B78eHGeePIAm98vQJvpu0").unwrap()).unwrap();
}
#[test]
fn derive_public_key() {
let licenses = Licenses::parse_ignore_expired(&base64::decode("AQA1hUFJiiSs0wFXkYuPUJVcDa6XCrZTcsvkB0Ffzz4CmwIITRXgCqeTYAcAAAAgQW5vbnltb3VzAAC4R+5mos+UQ/KCbkpQLMI5WRp4wkQu8e5PZY4zU+/FlyAJwaE8CcJJ/A==").unwrap()).unwrap();
let derived_key =
licenses.derive_public_key(EccKeyPubEd25519::from_bytes(crate::ROOT_KEY)).unwrap();
let expected_key = [
0x40, 0xe9, 0x50, 0xc4, 0x61, 0xba, 0x18, 0x3a, 0x1e, 0xb7, 0xcb, 0xb1, 0x9a, 0xc3,
0xd8, 0xd9, 0xc4, 0xd5, 0x24, 0xdb, 0x38, 0xf7, 0x2d, 0x3d, 0x66, 0x75, 0x77, 0x2a,
0xc5, 0x9c, 0xc5, 0xc6,
];
let derived_key = derived_key.compress().0;
assert_eq!(derived_key, expected_key);
}
}