use base64::{Engine, engine::general_purpose::URL_SAFE_NO_PAD};
use chrono::{DateTime, Utc};
use zeroize::Zeroizing;
use auths_core::crypto::signer::encrypt_keypair;
use auths_core::pairing::types::{SubmitConfirmationRequest, SubmitResponseRequest};
use auths_core::pairing::{
Base64UrlEncoded, GetConfirmationResponse, PairingResponse, PairingToken,
};
use auths_core::storage::keychain::{IdentityDID, KeyAlias, KeyRole, extract_public_key_bytes};
use auths_crypto::CurveType;
use auths_id::keri::delegation::{DeviceDipBundle, anchor_received_dip, build_device_dip};
use auths_id::keri::types::Prefix;
use auths_id::keri::{Event, parse_did_keri, validate_delegation};
use auths_keri::{DipEvent, IxnEvent, SourceSeal, serialize_source_seal_couples};
use auths_verifier::types::CanonicalDid;
use crate::context::AuthsContext;
use crate::pairing::PairingError;
#[derive(serde::Serialize, serde::Deserialize)]
struct WireSignedDip {
event: DipEvent,
attachment_b64: String,
}
fn encode_signed_dip(dip: &DipEvent, attachment: &[u8]) -> Result<String, PairingError> {
let wire = WireSignedDip {
event: dip.clone(),
attachment_b64: URL_SAFE_NO_PAD.encode(attachment),
};
let json = serde_json::to_vec(&wire)
.map_err(|e| PairingError::AttestationFailed(format!("encode dip: {e}")))?;
Ok(URL_SAFE_NO_PAD.encode(json))
}
fn decode_signed_dip(encoded: &str) -> Result<(DipEvent, Vec<u8>), PairingError> {
let json = URL_SAFE_NO_PAD
.decode(encoded)
.map_err(|e| PairingError::AttestationFailed(format!("decode dip envelope: {e}")))?;
let wire: WireSignedDip = serde_json::from_slice(&json)
.map_err(|e| PairingError::AttestationFailed(format!("decode dip json: {e}")))?;
let attachment = URL_SAFE_NO_PAD
.decode(&wire.attachment_b64)
.map_err(|e| PairingError::AttestationFailed(format!("decode dip attachment: {e}")))?;
Ok((wire.event, attachment))
}
fn encode_anchor_ixn(ixn: &IxnEvent) -> Result<String, PairingError> {
let json = serde_json::to_vec(ixn)
.map_err(|e| PairingError::AttestationFailed(format!("encode anchor ixn: {e}")))?;
Ok(URL_SAFE_NO_PAD.encode(json))
}
fn decode_anchor_ixn(encoded: &str) -> Result<IxnEvent, PairingError> {
let json = URL_SAFE_NO_PAD
.decode(encoded)
.map_err(|e| PairingError::AttestationFailed(format!("decode anchor envelope: {e}")))?;
serde_json::from_slice(&json)
.map_err(|e| PairingError::AttestationFailed(format!("decode anchor ixn: {e}")))
}
fn resolve_root_signing(ctx: &AuthsContext) -> Result<(Prefix, KeyAlias, CurveType), PairingError> {
let managed = ctx
.identity_storage
.load_identity()
.map_err(|e| PairingError::IdentityNotFound(e.to_string()))?;
let root_prefix = parse_did_keri(managed.controller_did.as_str())
.map_err(|e| PairingError::IdentityNotFound(format!("invalid root did:keri: {e}")))?;
#[allow(clippy::disallowed_methods)]
let controller_identity_did = IdentityDID::new_unchecked(managed.controller_did.to_string());
let aliases = ctx
.key_storage
.list_aliases_for_identity(&controller_identity_did)
.map_err(|e| PairingError::IdentityNotFound(e.to_string()))?;
let root_alias = aliases
.into_iter()
.find(|a| !a.contains("--next-"))
.ok_or_else(|| {
PairingError::IdentityNotFound(format!(
"no signing key found for {}",
managed.controller_did
))
})?;
let (_pk, root_curve) = extract_public_key_bytes(
ctx.key_storage.as_ref(),
&root_alias,
ctx.passphrase_provider.as_ref(),
)
.map_err(|e| PairingError::StorageError(format!("resolve root curve: {e}")))?;
Ok((root_prefix, root_alias, root_curve))
}
pub struct JoinerPending {
bundle: DeviceDipBundle,
device_alias: KeyAlias,
root_prefix: Prefix,
}
pub fn build_delegated_join_response(
now: DateTime<Utc>,
token: &PairingToken,
curve: CurveType,
device_alias: KeyAlias,
device_name: Option<String>,
) -> Result<(SubmitResponseRequest, JoinerPending, Zeroizing<[u8; 32]>), PairingError> {
let root_prefix = parse_did_keri(&token.controller_did).map_err(|e| {
PairingError::IdentityNotFound(format!("token controller did:keri invalid: {e}"))
})?;
let bundle = build_device_dip(&root_prefix, curve)
.map_err(|e| PairingError::AttestationFailed(format!("build device dip: {e}")))?;
let parsed = auths_crypto::parse_key_material(bundle.current_pkcs8.as_ref())
.map_err(|e| PairingError::KeyExchangeFailed(format!("parse device key: {e}")))?;
let device_didkey = CanonicalDid::from_public_key_did_key(&parsed.public_key, curve);
let (response, shared_secret) = PairingResponse::create(
now,
token,
&parsed.seed,
&parsed.public_key,
device_didkey.to_string(),
device_name,
)
.map_err(|e| PairingError::KeyExchangeFailed(e.to_string()))?;
let responder_inception_event = encode_signed_dip(&bundle.dip, &bundle.attachment)?;
let submit_req = SubmitResponseRequest {
device_ephemeral_pubkey: Base64UrlEncoded::from_raw(response.device_ephemeral_pubkey),
device_signing_pubkey: Base64UrlEncoded::from_raw(response.device_signing_pubkey),
curve: response.curve,
device_did: response.device_did.clone(),
signature: Base64UrlEncoded::from_raw(response.signature),
device_name: response.device_name,
subkey_chain: None,
initiator_inception_event: String::new(),
responder_inception_event,
shared_kel_inception_event: None,
};
Ok((
submit_req,
JoinerPending {
bundle,
device_alias,
root_prefix,
},
shared_secret,
))
}
pub fn finalize_delegated_join(
ctx: &AuthsContext,
pending: JoinerPending,
confirmation: &GetConfirmationResponse,
) -> Result<CanonicalDid, PairingError> {
if confirmation.aborted {
return Err(PairingError::SessionNotAvailable(
"initiator aborted the pairing (SAS mismatch)".to_string(),
));
}
let encoded = confirmation
.encrypted_attestation
.as_deref()
.ok_or_else(|| {
PairingError::StorageError("confirmation carried no anchoring event".to_string())
})?;
let anchor_ixn = decode_anchor_ixn(encoded)?;
if anchor_ixn.i != pending.root_prefix {
return Err(PairingError::AttestationFailed(format!(
"anchor authored by {} but expected the delegating root {}",
anchor_ixn.i, pending.root_prefix
)));
}
let JoinerPending {
mut bundle,
device_alias,
..
} = pending;
let source_seal = SourceSeal {
s: anchor_ixn.s,
d: anchor_ixn.d.clone(),
};
bundle.dip.source_seal = Some(source_seal.clone());
validate_delegation(&Event::Dip(bundle.dip.clone()), &[Event::Ixn(anchor_ixn)]).map_err(
|e| PairingError::AttestationFailed(format!("delegation not anchored by the root: {e}")),
)?;
let mut attachment = bundle.attachment.clone();
attachment.extend_from_slice(
&serialize_source_seal_couples(&[source_seal])
.map_err(|e| PairingError::AttestationFailed(format!("encode source seal: {e}")))?,
);
ctx.registry
.init_if_needed()
.map_err(|e| PairingError::StorageError(format!("init device registry: {e}")))?;
ctx.registry
.append_signed_event(
&bundle.device_prefix,
&Event::Dip(bundle.dip.clone()),
&attachment,
)
.map_err(|e| PairingError::StorageError(format!("persist device dip: {e}")))?;
let pass = ctx
.passphrase_provider
.get_passphrase(&format!(
"Create passphrase for device key '{}':",
device_alias
))
.map_err(|e| PairingError::StorageError(e.to_string()))?;
let enc_cur = encrypt_keypair(bundle.current_pkcs8.as_ref(), &pass)
.map_err(|e| PairingError::StorageError(e.to_string()))?;
ctx.key_storage
.store_key(
&device_alias,
&bundle.device_did,
KeyRole::Primary,
&enc_cur,
)
.map_err(|e| PairingError::StorageError(e.to_string()))?;
let next_alias = KeyAlias::new_unchecked(format!("{}--next-0", device_alias));
let enc_next = encrypt_keypair(bundle.next_pkcs8.as_ref(), &pass)
.map_err(|e| PairingError::StorageError(e.to_string()))?;
ctx.key_storage
.store_key(
&next_alias,
&bundle.device_did,
KeyRole::NextRotation,
&enc_next,
)
.map_err(|e| PairingError::StorageError(e.to_string()))?;
CanonicalDid::parse(bundle.device_did.as_str())
.map_err(|e| PairingError::AttestationFailed(format!("device did:keri parse: {e}")))
}
pub struct PairingAnchorResult {
pub device_did: CanonicalDid,
pub device_name: Option<String>,
pub confirmation: SubmitConfirmationRequest,
}
pub fn anchor_pairing_response(
ctx: &AuthsContext,
responder_inception_event: &str,
device_name: Option<String>,
) -> Result<PairingAnchorResult, PairingError> {
if responder_inception_event.is_empty() {
return Err(PairingError::AttestationFailed(
"pairing response carried no delegated inception event".to_string(),
));
}
let (dip, attachment) = decode_signed_dip(responder_inception_event)?;
let (root_prefix, root_alias, root_curve) = resolve_root_signing(ctx)?;
if dip.di != root_prefix {
return Err(PairingError::AttestationFailed(format!(
"device dip delegates to {} but this identity is {}",
dip.di, root_prefix
)));
}
let (device_did_keri, anchor_ixn) = anchor_received_dip(
ctx.registry.as_ref(),
&root_prefix,
&root_alias,
root_curve,
&dip,
&attachment,
ctx.passphrase_provider.as_ref(),
ctx.key_storage.as_ref(),
)
.map_err(|e| PairingError::AttestationFailed(format!("anchor delegated device: {e}")))?;
let confirmation = SubmitConfirmationRequest {
encrypted_attestation: Some(encode_anchor_ixn(&anchor_ixn)?),
aborted: false,
};
let device_did = CanonicalDid::parse(device_did_keri.as_str())
.map_err(|e| PairingError::AttestationFailed(format!("device did:keri parse: {e}")))?;
Ok(PairingAnchorResult {
device_did,
device_name,
confirmation,
})
}