im-core 0.1.0

Rust IM SDK for Awiki clients built on Agent Network Protocol (ANP)
Documentation
use anp::PrivateKeyMaterial;
use rusqlite::Connection;
use serde_json::Value;

use super::client::DirectSecureClientInput;

pub(crate) struct DirectSecureIdentityMaterial {
    pub(crate) owner_identity_id: String,
    pub(crate) owner_did: String,
    pub(crate) identity_name: String,
    pub(crate) signing_key_id: String,
    pub(crate) agreement_key_id: String,
    pub(crate) signing_private_pem: String,
    pub(crate) agreement_private_pem: String,
    pub(crate) local_did_document: Value,
}

impl DirectSecureIdentityMaterial {
    pub(crate) fn client_input<'a>(
        &self,
        local_state: &'a Connection,
    ) -> DirectSecureClientInput<'a> {
        DirectSecureClientInput {
            owner_identity_id: self.owner_identity_id.clone(),
            owner_did: self.owner_did.clone(),
            identity_name: self.identity_name.clone(),
            signing_key_id: self.signing_key_id.clone(),
            agreement_key_id: self.agreement_key_id.clone(),
            signing_private_pem: self.signing_private_pem.clone(),
            agreement_private_pem: self.agreement_private_pem.clone(),
            local_did_document: self.local_did_document.clone(),
            local_state,
        }
    }
}

pub(crate) struct DirectSecureAgreementMaterial {
    pub(crate) agreement_key_id: String,
    pub(crate) agreement_private_pem: String,
}

pub(crate) fn local_identity_material(
    client: &crate::core::ImClient,
) -> crate::ImResult<DirectSecureIdentityMaterial> {
    let runtime = client.runtime();
    let local_did_document = runtime.key_provider.did_document()?;
    let signing_private_pem = runtime.key_provider.default_signing_private_pem()?;
    let agreement_private_pem = runtime.key_provider.e2ee_agreement_private_pem()?;
    let owner_did = client.did().as_str().to_owned();
    Ok(DirectSecureIdentityMaterial {
        owner_identity_id: client.current_identity().id.as_str().to_owned(),
        owner_did: owner_did.clone(),
        identity_name: client
            .current_identity()
            .local_alias
            .clone()
            .unwrap_or_else(|| client.current_identity().id.as_str().to_owned()),
        signing_key_id: format!("{owner_did}#key-1"),
        agreement_key_id: format!("{owner_did}#key-3"),
        signing_private_pem,
        agreement_private_pem,
        local_did_document,
    })
}

pub(crate) fn agreement_material(
    client: &crate::core::ImClient,
) -> crate::ImResult<DirectSecureAgreementMaterial> {
    let owner_did = client.did().as_str().to_owned();
    Ok(DirectSecureAgreementMaterial {
        agreement_key_id: format!("{owner_did}#key-3"),
        agreement_private_pem: client.runtime().key_provider.e2ee_agreement_private_pem()?,
    })
}

pub(crate) fn agreement_private_key(
    client: &crate::core::ImClient,
) -> crate::ImResult<PrivateKeyMaterial> {
    let material = agreement_material(client)?;
    PrivateKeyMaterial::from_pem(&material.agreement_private_pem).map_err(|err| {
        crate::ImError::Serialization {
            detail: format!("parse direct E2EE agreement private key: {err}"),
        }
    })
}

pub(crate) fn local_did_document(client: &crate::core::ImClient) -> crate::ImResult<Value> {
    client.runtime().key_provider.did_document()
}

#[cfg(test)]
mod tests {
    #[test]
    fn secure_direct_key_material_scanner_rejects_runtime_secret_path_reads() {
        let manifest_dir = std::path::Path::new(env!("CARGO_MANIFEST_DIR"));
        let files = [
            "src/internal/secure_direct/prepare.rs",
            "src/internal/secure_direct/send.rs",
            "src/internal/secure_direct/incoming.rs",
            "src/internal/secure_direct/async_send.rs",
            "src/internal/secure_direct/async_receive.rs",
        ];
        let forbidden = [
            "runtime.private_key_path",
            "runtime.e2ee_agreement_private_key_path",
            "runtime.did_document_path",
            ".private_key_path.clone()",
            ".e2ee_agreement_private_key_path.clone()",
            ".did_document_path.clone()",
        ];
        for file in files {
            let path = manifest_dir.join(file);
            let source = std::fs::read_to_string(&path).expect("secure direct source should read");
            for pattern in forbidden {
                assert!(
                    !source.contains(pattern),
                    "{file} must not contain `{pattern}`; use secure_direct::identity_material"
                );
            }
        }
    }
}