pub mod crypto;
pub mod key;
pub mod svp;
use std::{
error::Error,
ffi::{c_char, c_void},
fmt::Display,
ptr::null_mut,
};
use bitflags::bitflags;
use chrono::{DateTime, NaiveDate, Utc};
use secapi_sys as ffi;
use uuid::Uuid;
pub use ffi::sa_version as Version;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ErrorStatus {
NoAvailableResourceSlot,
InvalidKeyFormat,
InvalidKeyType,
NullParameter,
InvalidParameter,
OperationNotAllowed,
InvalidSvpBuffer,
OperationNotSupported,
SelfTest,
VerificationFailed,
InternalError,
HardwareError,
}
impl Error for ErrorStatus {}
impl Display for ErrorStatus {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::NoAvailableResourceSlot => write!(f, "No Available Resource Slot"),
Self::InvalidKeyFormat => write!(f, "Invalid Key Format"),
Self::InvalidKeyType => write!(f, "Invalid Key Type"),
Self::NullParameter => write!(f, "Null Parameter"),
Self::InvalidParameter => write!(f, "Invalid Parameter"),
Self::OperationNotAllowed => write!(f, "Operation Not Allowed"),
Self::InvalidSvpBuffer => write!(f, "Invalid Svp Buffer"),
Self::OperationNotSupported => write!(f, "Operation Not Supported"),
Self::SelfTest => write!(f, "Self Test"),
Self::VerificationFailed => write!(f, "Verification Failed"),
Self::InternalError => write!(f, "Internal Error"),
Self::HardwareError => write!(f, "Hardware Error"),
}
}
}
fn convert_result(sa_status: ffi::sa_status) -> Result<(), ErrorStatus> {
match sa_status {
ffi::sa_status::SA_STATUS_OK => Ok(()),
ffi::sa_status::SA_STATUS_NO_AVAILABLE_RESOURCE_SLOT => {
Err(ErrorStatus::NoAvailableResourceSlot)
}
ffi::sa_status::SA_STATUS_INVALID_KEY_FORMAT => Err(ErrorStatus::InvalidKeyFormat),
ffi::sa_status::SA_STATUS_INVALID_KEY_TYPE => Err(ErrorStatus::InvalidKeyType),
ffi::sa_status::SA_STATUS_NULL_PARAMETER => Err(ErrorStatus::NullParameter),
ffi::sa_status::SA_STATUS_INVALID_PARAMETER => Err(ErrorStatus::InvalidParameter),
ffi::sa_status::SA_STATUS_OPERATION_NOT_ALLOWED => Err(ErrorStatus::OperationNotAllowed),
ffi::sa_status::SA_STATUS_INVALID_SVP_BUFFER => Err(ErrorStatus::InvalidSvpBuffer),
ffi::sa_status::SA_STATUS_OPERATION_NOT_SUPPORTED => {
Err(ErrorStatus::OperationNotSupported)
}
ffi::sa_status::SA_STATUS_SELF_TEST => Err(ErrorStatus::SelfTest),
ffi::sa_status::SA_STATUS_VERIFICATION_FAILED => Err(ErrorStatus::VerificationFailed),
ffi::sa_status::SA_STATUS_INTERNAL_ERROR => Err(ErrorStatus::InternalError),
ffi::sa_status::SA_STATUS_HW_ERROR => Err(ErrorStatus::HardwareError),
_ => panic!("invalid sa_status: {sa_status:?}"),
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub enum EllipticCurve {
NistP192,
NistP224,
NistP256,
NistP384,
NistP521,
ED25519,
X25519,
ED448,
X448,
}
impl From<EllipticCurve> for ffi::sa_elliptic_curve {
fn from(value: EllipticCurve) -> Self {
match value {
EllipticCurve::NistP192 => ffi::sa_elliptic_curve::SA_ELLIPTIC_CURVE_NIST_P192,
EllipticCurve::NistP224 => ffi::sa_elliptic_curve::SA_ELLIPTIC_CURVE_NIST_P224,
EllipticCurve::NistP256 => ffi::sa_elliptic_curve::SA_ELLIPTIC_CURVE_NIST_P256,
EllipticCurve::NistP384 => ffi::sa_elliptic_curve::SA_ELLIPTIC_CURVE_NIST_P384,
EllipticCurve::NistP521 => ffi::sa_elliptic_curve::SA_ELLIPTIC_CURVE_NIST_P521,
EllipticCurve::ED25519 => ffi::sa_elliptic_curve::SA_ELLIPTIC_CURVE_ED25519,
EllipticCurve::X25519 => ffi::sa_elliptic_curve::SA_ELLIPTIC_CURVE_X25519,
EllipticCurve::ED448 => ffi::sa_elliptic_curve::SA_ELLIPTIC_CURVE_ED448,
EllipticCurve::X448 => ffi::sa_elliptic_curve::SA_ELLIPTIC_CURVE_X448,
}
}
}
bitflags! {
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
struct UsageFlags: u64 {
const EXCHANGE = 0x00_01;
const DERIVE = 0x00_02;
const UNWRAP = 0x00_04;
const ENCRYPT = 0x00_08;
const DECRYPT = 0x00_10;
const SIGN = 0x00_20;
const ALLOWED_ANALOG_UNPROTECTED = 0x00_40;
const ALLOWED_ANALOG_CGMSA = 0x00_80;
const ALLOWED_DIGITAL_UNPROTECTED = 0x01_00;
const ALLOWED_DIGITAL_HDCP14 = 0x02_00;
const ALLOWED_DIGITAL_HDCP22 = 0x04_00;
const ALLOWED_DIGITAL_DTCP = 0x08_00;
const SVP_OPTIONAL = 0x10_00;
const CACHEABLE = 0x20_00;
const ALL_OUTPUT_PROTECTIONS =
Self::ALLOWED_ANALOG_UNPROTECTED.bits() |
Self::ALLOWED_ANALOG_CGMSA.bits() |
Self::ALLOWED_DIGITAL_UNPROTECTED.bits() |
Self::ALLOWED_DIGITAL_HDCP14.bits() |
Self::ALLOWED_DIGITAL_HDCP22.bits() |
Self::ALLOWED_DIGITAL_DTCP.bits() |
Self::SVP_OPTIONAL.bits();
const ALL_KEY_RIGHTS =
Self::EXCHANGE.bits() |
Self::DERIVE.bits() |
Self::UNWRAP.bits() |
Self::ENCRYPT.bits() |
Self::DECRYPT.bits() |
Self::SIGN.bits();
const ALL_RIGHTS =
Self::ALL_KEY_RIGHTS.bits() |
Self::ALL_OUTPUT_PROTECTIONS.bits() |
Self::CACHEABLE.bits();
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Rights {
id: [c_char; 64],
usage_flags: UsageFlags,
child_usage_flags: UsageFlags,
not_before: DateTime<Utc>,
not_on_or_after: DateTime<Utc>,
allowed_tas: [Uuid; ffi::MAX_NUM_ALLOWED_TA_IDS],
}
impl Rights {
const ALLOW_ALL_TAS: [Uuid; ffi::MAX_NUM_ALLOWED_TA_IDS] = [Uuid::from_bytes([
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff,
]); ffi::MAX_NUM_ALLOWED_TA_IDS];
pub fn allow_all() -> Self {
Self {
id: [0; 64],
usage_flags: UsageFlags::EXCHANGE
| UsageFlags::DERIVE
| UsageFlags::UNWRAP
| UsageFlags::ENCRYPT
| UsageFlags::DECRYPT
| UsageFlags::SIGN
| UsageFlags::ALL_OUTPUT_PROTECTIONS
| UsageFlags::CACHEABLE,
child_usage_flags: UsageFlags::empty(),
not_before: DateTime::from_timestamp(0, 0).expect("Could not represent DateTime"),
not_on_or_after: NaiveDate::from_ymd_opt(262142, 12, 31)
.expect("Could not represent NaiveDate")
.and_hms_opt(0, 0, 0)
.expect("Could not represent NaiveDateTime")
.and_utc(),
allowed_tas: Self::ALLOW_ALL_TAS,
}
}
}
impl From<ffi::sa_rights> for Rights {
fn from(value: ffi::sa_rights) -> Self {
let mut allowed_tas = [Uuid::from_bytes([
0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8,
]); ffi::MAX_NUM_ALLOWED_TA_IDS];
for (i, &uuid) in value.allowed_tas.iter().enumerate() {
allowed_tas[i] = Uuid::from_bytes(uuid.id);
}
Self {
id: value.id,
usage_flags: UsageFlags::from_bits_truncate(value.usage_flags),
child_usage_flags: UsageFlags::from_bits_truncate(value.child_usage_flags),
not_before: DateTime::from_timestamp(value.not_before as i64, 0).unwrap(),
not_on_or_after: DateTime::from_timestamp(value.not_on_or_after as i64, 0).unwrap(),
allowed_tas,
}
}
}
impl From<&ffi::sa_rights> for Rights {
fn from(value: &ffi::sa_rights) -> Self {
let mut allowed_tas = [Uuid::from_bytes([
0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8,
]); ffi::MAX_NUM_ALLOWED_TA_IDS];
for (i, &uuid) in value.allowed_tas.iter().enumerate() {
allowed_tas[i] = Uuid::from_bytes(uuid.id);
}
Self {
id: value.id,
usage_flags: UsageFlags::from_bits_truncate(value.usage_flags),
child_usage_flags: UsageFlags::from_bits_truncate(value.child_usage_flags),
not_before: DateTime::from_timestamp(value.not_before as i64, 0).unwrap(),
not_on_or_after: DateTime::from_timestamp(value.not_on_or_after as i64, 0).unwrap(),
allowed_tas,
}
}
}
impl From<Rights> for ffi::sa_rights {
fn from(value: Rights) -> Self {
let mut allowed_tas = [ffi::sa_uuid {
id: [
0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8,
],
}; ffi::MAX_NUM_ALLOWED_TA_IDS];
for (i, &uuid) in value.allowed_tas.iter().enumerate() {
allowed_tas[i] = ffi::sa_uuid {
id: *uuid.as_bytes(),
};
}
Self {
id: value.id,
usage_flags: value.usage_flags.bits(),
child_usage_flags: value.child_usage_flags.bits(),
not_before: value.not_before.timestamp() as u64,
not_on_or_after: value.not_on_or_after.timestamp() as u64,
allowed_tas,
}
}
}
impl From<&Rights> for ffi::sa_rights {
fn from(value: &Rights) -> Self {
let mut allowed_tas = [ffi::sa_uuid {
id: [
0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8,
],
}; ffi::MAX_NUM_ALLOWED_TA_IDS];
for (i, &uuid) in value.allowed_tas.iter().enumerate() {
allowed_tas[i] = ffi::sa_uuid {
id: *uuid.as_bytes(),
};
}
Self {
id: value.id,
usage_flags: value.usage_flags.bits(),
child_usage_flags: value.child_usage_flags.bits(),
not_before: value.not_before.timestamp() as u64,
not_on_or_after: value.not_on_or_after.timestamp() as u64,
allowed_tas,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub enum DigestAlgorithm {
SHA1,
SHA256,
SHA384,
SHA512,
}
impl From<DigestAlgorithm> for ffi::sa_digest_algorithm {
fn from(value: DigestAlgorithm) -> Self {
match value {
DigestAlgorithm::SHA1 => ffi::sa_digest_algorithm::SA_DIGEST_ALGORITHM_SHA1,
DigestAlgorithm::SHA256 => ffi::sa_digest_algorithm::SA_DIGEST_ALGORITHM_SHA256,
DigestAlgorithm::SHA384 => ffi::sa_digest_algorithm::SA_DIGEST_ALGORITHM_SHA384,
DigestAlgorithm::SHA512 => ffi::sa_digest_algorithm::SA_DIGEST_ALGORITHM_SHA512,
}
}
}
trait FfiParameters {
fn ffi_ptr(&mut self) -> *mut c_void;
}
pub fn version() -> Result<Version, ErrorStatus> {
let mut sa_version = ffi::sa_version {
specification_major: 0,
specification_minor: 0,
specification_revision: 0,
implementation_revision: 0,
};
convert_result(unsafe { ffi::sa_get_version(&mut sa_version) })?;
Ok(sa_version)
}
pub fn name() -> Result<String, ErrorStatus> {
let mut name_size: usize = 0;
convert_result(unsafe { ffi::sa_get_name(null_mut(), &mut name_size) })?;
let mut name_buffer: Vec<c_char> = vec![0; name_size];
convert_result(unsafe { ffi::sa_get_name(name_buffer.as_mut_ptr(), &mut name_size) })?;
String::from_utf8(
name_buffer
.into_iter()
.take_while(|value| *value != 0)
.map(|value| value as _)
.collect(),
)
.map_err(|_| ErrorStatus::InvalidParameter)
}
pub type DeviceId = [u8; 8];
pub fn device_id() -> Result<DeviceId, ErrorStatus> {
let mut device_id = 0;
convert_result(unsafe { ffi::sa_get_device_id(&mut device_id) })?;
Ok(device_id.to_ne_bytes())
}
pub fn ta_uuid() -> Result<Uuid, ErrorStatus> {
let mut sa_uuid = ffi::sa_uuid { id: [0; 16] };
convert_result(unsafe { ffi::sa_get_ta_uuid(&mut sa_uuid) })?;
Ok(Uuid::from_bytes(sa_uuid.id))
}
#[cfg(test)]
mod test {
use super::*;
#[test]
fn test_secapi_version() -> Result<(), ErrorStatus> {
let secapi_version = version()?;
assert_eq!(secapi_version.specification_major, 3);
assert_eq!(secapi_version.specification_minor, 4);
assert_eq!(secapi_version.specification_revision, 1);
assert_eq!(secapi_version.implementation_revision, 0);
Ok(())
}
#[test]
fn test_secapi_name() -> Result<(), ErrorStatus> {
let secapi_name = name()?;
assert_eq!(secapi_name, "Reference");
Ok(())
}
#[test]
fn test_device_id() -> Result<(), ErrorStatus> {
let device_id = device_id()?;
assert_eq!(device_id, [0xfe, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff]);
Ok(())
}
#[test]
fn test_ta_uuid() -> Result<(), ErrorStatus> {
let uuid = ta_uuid()?;
assert_eq!(
uuid,
Uuid::from_bytes([
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x01
])
);
Ok(())
}
}