use crate::{signing::malformed, *};
use candid::Principal;
use cose2::Sign1Message;
use dmsg_types::{agent::DelegationAuthority, cose::*, user::*, *};
use serde_bytes::ByteArray;
pub trait DeviceInputExt {
fn validate(&self) -> Result<()>;
}
impl DeviceInputExt for DeviceInput {
fn validate(&self) -> Result<()> {
nonzero(self.device_id.as_slice())?;
nonzero(self.hpke_pub.as_slice())?;
ensure_valid(
!self.capabilities.is_empty() && self.capabilities.len() <= 5,
"capabilities",
)?;
ensure_valid(
self.capabilities
.iter()
.enumerate()
.all(|(i, capability)| !self.capabilities[..i].contains(capability)),
"duplicate capability",
)?;
validate_ed25519_key(self.signing_pub.as_slice())
}
}
pub struct PreparedAttestation {
pub to_be_signed: Vec<u8>,
pub cose_key: Vec<u8>,
pub thumbprint: Hash,
pub purpose: KeyPurpose,
message: Sign1Message,
}
impl PreparedAttestation {
pub fn finish(mut self, signature: &[u8]) -> Result<SignedArtifact> {
ensure(signature.len() == 64, Error::IntegrityFailed)?;
self.message
.set_signature(signature.to_vec())
.map_err(malformed)?;
Ok(SignedArtifact {
cose_sign1: self.message.to_vec().map_err(malformed)?.into(),
cose_key: self.cose_key.into(),
})
}
}
pub fn prepare_attestation(statement: &Statement, signing_pub: &Hash) -> Result<PreparedAttestation> {
let thumbprint = key_thumbprint(&public_cose_key(&[], signing_pub.as_slice())?)?;
let cose_key = public_cose_key(thumbprint.as_slice(), signing_pub.as_slice())?;
let (message, to_be_signed) = prepare_cose(statement, thumbprint.as_slice())?;
Ok(PreparedAttestation {
to_be_signed,
cose_key,
thumbprint,
purpose: statement_purpose(statement),
message,
})
}
pub fn app_action_statement(request: &AppActionAttestRequest) -> Result<Statement> {
ensure(
request.account_id == request.action.signing_account,
Error::Forbidden,
)?;
ensure(
request.approval.expires_at <= request.action.expires_at_ms,
Error::Expired,
)?;
Ok(Statement {
issuer: request.issuer.clone(),
subject: None,
issued_at: None,
content: StatementContent::AppAction(Box::new(request.action.clone())),
})
}
pub fn content_root_context(environment: &Environment, derivation_version: u16) -> Vec<u8> {
canonical(&("dmsg/content-root/v2", environment, derivation_version))
}
pub trait CoseInitExt {
fn validate(&self, id: Principal) -> Result<()>;
}
impl CoseInitExt for CoseInit {
fn validate(&self, id: Principal) -> Result<()> {
ensure_valid(
self.executing_canister == id && self.derivation_version == 2,
"immutable key home/version",
)?;
validate_namespace(&self.issuer_namespace)?;
crate::agent::validate_user_homes(
&self.environment,
&self.issuer_namespace,
&self.user_homes,
)?;
authenticated(self.governance)?;
if self.environment == Environment::Production {
ensure_valid(self.master.key_name == "key_1", "production requires key_1")?;
nonzero(self.master.expected_fingerprint.as_slice())?;
} else {
ensure_valid(
matches!(
self.master.key_name.as_str(),
"key_1" | "test_key_1" | "dfx_test_key"
),
"key name",
)?;
}
ensure_valid(
self.daily_executions > 0 && self.daily_cycles > 0,
"hard budgets",
)
}
}
pub const ATTEST_APPROVAL_DOMAIN: &str = "dmsg/attest/v1";
pub fn attest_approval_command<'a>(
statement: &'a Statement,
origin: &'a str,
signature: &'a Ed25519Signature,
) -> (&'a Statement, &'a str, &'a Ed25519Signature) {
(statement, origin, signature)
}
pub const DERIVE_APPROVAL_DOMAIN: &str = "dmsg/derive-root/v1";
pub fn derive_approval_command(request: &DeriveRootRequest) -> (u64, &ByteArray<48>, u128) {
(
request.generation,
&request.transport_public_key,
request.max_cycles,
)
}
pub fn recovery_device_message(home: Principal, account_id: &AccountId, request: &RecoveryRequest) -> Hash {
digest("dmsg/recovery-device/v1", &(home, account_id, request))
}
pub fn account_admission_message(home: Principal, caller: Principal, expires_at: u64) -> Hash {
digest("dmsg/account-admission/v1", &(home, caller, expires_at))
}
pub fn controller_pop_message(
home: Principal,
account_id: &AccountId,
generation: u32,
delegation: &DelegationAuthority,
request_id: OpId,
) -> Hash {
digest(
"dmsg/controller-pop/v1",
&(home, account_id, generation, delegation, request_id),
)
}
pub fn root_recipients_digest(devices: &[Hash], generation: u64) -> Hash {
let mut ids: Vec<Hash> = devices.to_vec();
ids.sort_unstable();
digest("dmsg/root-recipients/1", &(ids, generation))
}
pub fn root_bundle_digest(recipients_digest: Hash, body_digest: Hash) -> Hash {
digest(
"dmsg/root-bundle-digest/2",
&(recipients_digest, body_digest),
)
}
#[cfg(test)]
mod tests;