use crate::error::StorageError;
#[cfg(feature = "git-storage")]
use crate::storage::git_refs::aggregate_canonical_refs;
use pkcs8::{
AlgorithmIdentifierRef, PrivateKeyInfo,
der::{Decode, Encode},
};
#[cfg(feature = "git-storage")]
use std::collections::HashMap;
use thiserror::Error;
use ring::rand::SystemRandom;
use ring::signature::Ed25519KeyPair;
use crate::storage::attestation::AttestationSource;
use auths_verifier::core::{Attestation, ResourceId};
use auths_verifier::types::{CanonicalDid, IdentityDID};
pub use crate::identity::managed::ManagedIdentity;
const OID_ED25519: pkcs8::der::asn1::ObjectIdentifier =
pkcs8::der::asn1::ObjectIdentifier::new_unwrap("1.3.101.112");
#[derive(Debug, Clone)]
pub struct Identity {
pub did: IdentityDID,
pub rid: ResourceId,
pub device_dids: Vec<CanonicalDid>,
}
impl Identity {
pub fn fetch_attestations(
&self,
source: &dyn AttestationSource,
) -> Result<Vec<Attestation>, StorageError> {
let mut all_attestations = Vec::new();
for device_did in &self.device_dids {
let device_attestations = source.load_attestations_for_device(device_did)?;
all_attestations.extend(device_attestations);
}
Ok(all_attestations)
}
#[cfg(feature = "git-storage")]
pub fn canonical_refs(
&self,
repo: &git2::Repository,
) -> Result<HashMap<String, String>, StorageError> {
aggregate_canonical_refs(repo, &self.device_dids)
}
}
#[derive(Error, Debug)]
#[non_exhaustive]
pub enum IdentityError {
#[error("KERI error: {0}")]
Keri(String),
#[error("PKCS#8 encoding error: {0}")]
Pkcs8EncodeError(String),
#[error("PKCS#8 decoding error: {0}")]
Pkcs8DecodeError(String),
#[error("Passphrase required")]
EmptyPassphrase,
#[error("Invalid key length: expected 32, got {0}")]
InvalidKeyLength(usize),
#[error("Key storage error: {0}")]
KeyStorage(String),
#[error("Key retrieval error: {0}")]
KeyRetrieval(String),
#[error("Ring crypto error: {0}")]
RingError(String),
}
impl From<ring::error::Unspecified> for IdentityError {
fn from(err: ring::error::Unspecified) -> Self {
IdentityError::RingError(format!("Unspecified ring error: {err}"))
}
}
impl From<ring::error::KeyRejected> for IdentityError {
fn from(err: ring::error::KeyRejected) -> Self {
IdentityError::RingError(format!("Ring key rejected: {err}"))
}
}
impl From<pkcs8::der::Error> for IdentityError {
fn from(err: pkcs8::der::Error) -> Self {
IdentityError::Pkcs8DecodeError(err.to_string())
}
}
impl auths_core::error::AuthsErrorInfo for IdentityError {
fn error_code(&self) -> &'static str {
match self {
Self::Keri(_) => "AUTHS-E4401",
Self::Pkcs8EncodeError(_) => "AUTHS-E4402",
Self::Pkcs8DecodeError(_) => "AUTHS-E4403",
Self::EmptyPassphrase => "AUTHS-E4404",
Self::InvalidKeyLength(_) => "AUTHS-E4405",
Self::KeyStorage(_) => "AUTHS-E4406",
Self::KeyRetrieval(_) => "AUTHS-E4407",
Self::RingError(_) => "AUTHS-E4408",
}
}
fn suggestion(&self) -> Option<&'static str> {
match self {
Self::Keri(_) => Some("KERI operation failed; check identity state"),
Self::Pkcs8EncodeError(_) => None,
Self::Pkcs8DecodeError(_) => Some("The key may be in an unsupported format"),
Self::EmptyPassphrase => Some("Provide a non-empty passphrase"),
Self::InvalidKeyLength(_) => Some("Expected a 32-byte Ed25519 key"),
Self::KeyStorage(_) => Some("Check keychain permissions"),
Self::KeyRetrieval(_) => Some("Check that the key alias exists in the keychain"),
Self::RingError(_) => None,
}
}
}
pub fn extract_seed_bytes(pkcs8_bytes: &[u8]) -> Result<&[u8], IdentityError> {
let pk_info = PrivateKeyInfo::from_der(pkcs8_bytes)?;
match pk_info.private_key.len() {
32 => Ok(pk_info.private_key),
34 => Ok(&pk_info.private_key[2..]),
other => Err(IdentityError::InvalidKeyLength(other)),
}
}
pub fn encode_seed_as_pkcs8(seed_bytes: &[u8]) -> Result<Vec<u8>, IdentityError> {
let mut wrapped_seed = Vec::with_capacity(34);
wrapped_seed.push(0x04);
wrapped_seed.push(0x20);
wrapped_seed.extend_from_slice(seed_bytes);
PrivateKeyInfo {
algorithm: AlgorithmIdentifierRef {
oid: OID_ED25519,
parameters: None,
},
private_key: &wrapped_seed,
public_key: None,
}
.to_der()
.map_err(|e| IdentityError::Pkcs8EncodeError(e.to_string()))
}
pub fn load_keypair_from_der_or_seed(bytes: &[u8]) -> Result<Ed25519KeyPair, IdentityError> {
if let Ok(kp) = Ed25519KeyPair::from_pkcs8_maybe_unchecked(bytes) {
return Ok(kp);
}
let seed = auths_crypto::parse_ed25519_seed(bytes)
.map_err(|e| IdentityError::Pkcs8DecodeError(format!("Cannot parse key data: {e}")))?;
let pkcs8_bytes = encode_seed_as_pkcs8(seed.as_bytes())?;
Ed25519KeyPair::from_pkcs8_maybe_unchecked(&pkcs8_bytes)
.map_err(|e| IdentityError::RingError(format!("Failed to load Ed25519 keypair: {e}")))
}
pub fn generate_keypair_with_seed(
rng: &SystemRandom,
) -> Result<(Ed25519KeyPair, Vec<u8>), IdentityError> {
let pkcs8_doc = Ed25519KeyPair::generate_pkcs8(rng)?;
let pkcs8_bytes = pkcs8_doc.as_ref().to_vec();
let keypair = Ed25519KeyPair::from_pkcs8(&pkcs8_bytes)?;
Ok((keypair, pkcs8_bytes))
}