auths_id/identity/
helpers.rs1use crate::error::StorageError;
2#[cfg(feature = "git-storage")]
3use crate::storage::git_refs::aggregate_canonical_refs;
4use pkcs8::{
5 AlgorithmIdentifierRef, PrivateKeyInfo,
6 der::{Decode, Encode},
7};
8#[cfg(feature = "git-storage")]
9use std::collections::HashMap;
10use thiserror::Error;
11
12use ring::rand::SystemRandom;
13use ring::signature::Ed25519KeyPair;
14
15use crate::storage::attestation::AttestationSource;
16
17use auths_verifier::core::{Attestation, ResourceId};
18use auths_verifier::types::{DeviceDID, IdentityDID};
19
20pub use crate::identity::managed::ManagedIdentity;
21
22const OID_ED25519: pkcs8::der::asn1::ObjectIdentifier =
23 pkcs8::der::asn1::ObjectIdentifier::new_unwrap("1.3.101.112");
24
25#[derive(Debug, Clone)]
26pub struct Identity {
27 pub did: IdentityDID,
28 pub rid: ResourceId,
29 pub device_dids: Vec<DeviceDID>,
30}
31
32impl Identity {
33 pub fn fetch_attestations(
34 &self,
35 source: &dyn AttestationSource,
36 ) -> Result<Vec<Attestation>, StorageError> {
37 let mut all_attestations = Vec::new();
38 for device_did in &self.device_dids {
39 let device_attestations = source.load_attestations_for_device(device_did)?;
40 all_attestations.extend(device_attestations);
41 }
42 Ok(all_attestations)
43 }
44
45 #[cfg(feature = "git-storage")]
46 pub fn canonical_refs(
47 &self,
48 repo: &git2::Repository,
49 ) -> Result<HashMap<String, String>, StorageError> {
50 aggregate_canonical_refs(repo, &self.device_dids)
51 }
52}
53
54#[derive(Error, Debug)]
55#[non_exhaustive]
56pub enum IdentityError {
57 #[error("KERI error: {0}")]
58 Keri(String),
59 #[error("PKCS#8 encoding error: {0}")]
60 Pkcs8EncodeError(String),
61 #[error("PKCS#8 decoding error: {0}")]
62 Pkcs8DecodeError(String),
63 #[error("Passphrase required")]
64 EmptyPassphrase,
65 #[error("Invalid key length: expected 32, got {0}")]
66 InvalidKeyLength(usize),
67 #[error("Key storage error: {0}")]
68 KeyStorage(String),
69 #[error("Key retrieval error: {0}")]
70 KeyRetrieval(String),
71 #[error("Ring crypto error: {0}")]
72 RingError(String),
73}
74
75impl From<ring::error::Unspecified> for IdentityError {
76 fn from(err: ring::error::Unspecified) -> Self {
77 IdentityError::RingError(format!("Unspecified ring error: {err}"))
78 }
79}
80
81impl From<ring::error::KeyRejected> for IdentityError {
82 fn from(err: ring::error::KeyRejected) -> Self {
83 IdentityError::RingError(format!("Ring key rejected: {err}"))
84 }
85}
86
87impl From<pkcs8::der::Error> for IdentityError {
88 fn from(err: pkcs8::der::Error) -> Self {
89 IdentityError::Pkcs8DecodeError(err.to_string())
90 }
91}
92
93impl auths_core::error::AuthsErrorInfo for IdentityError {
94 fn error_code(&self) -> &'static str {
95 match self {
96 Self::Keri(_) => "AUTHS-E4401",
97 Self::Pkcs8EncodeError(_) => "AUTHS-E4402",
98 Self::Pkcs8DecodeError(_) => "AUTHS-E4403",
99 Self::EmptyPassphrase => "AUTHS-E4404",
100 Self::InvalidKeyLength(_) => "AUTHS-E4405",
101 Self::KeyStorage(_) => "AUTHS-E4406",
102 Self::KeyRetrieval(_) => "AUTHS-E4407",
103 Self::RingError(_) => "AUTHS-E4408",
104 }
105 }
106
107 fn suggestion(&self) -> Option<&'static str> {
108 match self {
109 Self::Keri(_) => Some("KERI operation failed; check identity state"),
110 Self::Pkcs8EncodeError(_) => None,
111 Self::Pkcs8DecodeError(_) => Some("The key may be in an unsupported format"),
112 Self::EmptyPassphrase => Some("Provide a non-empty passphrase"),
113 Self::InvalidKeyLength(_) => Some("Expected a 32-byte Ed25519 key"),
114 Self::KeyStorage(_) => Some("Check keychain permissions"),
115 Self::KeyRetrieval(_) => Some("Check that the key alias exists in the keychain"),
116 Self::RingError(_) => None,
117 }
118 }
119}
120
121pub fn extract_seed_bytes(pkcs8_bytes: &[u8]) -> Result<&[u8], IdentityError> {
123 let pk_info = PrivateKeyInfo::from_der(pkcs8_bytes)?;
124 match pk_info.private_key.len() {
125 32 => Ok(pk_info.private_key),
126 34 => Ok(&pk_info.private_key[2..]),
127 other => Err(IdentityError::InvalidKeyLength(other)),
128 }
129}
130
131pub fn encode_seed_as_pkcs8(seed_bytes: &[u8]) -> Result<Vec<u8>, IdentityError> {
134 let mut wrapped_seed = Vec::with_capacity(34);
135 wrapped_seed.push(0x04);
136 wrapped_seed.push(0x20);
137 wrapped_seed.extend_from_slice(seed_bytes);
138
139 PrivateKeyInfo {
140 algorithm: AlgorithmIdentifierRef {
141 oid: OID_ED25519,
142 parameters: None,
143 },
144 private_key: &wrapped_seed,
145 public_key: None,
146 }
147 .to_der()
148 .map_err(|e| IdentityError::Pkcs8EncodeError(e.to_string()))
149}
150
151pub fn load_keypair_from_der_or_seed(bytes: &[u8]) -> Result<Ed25519KeyPair, IdentityError> {
156 if let Ok(kp) = Ed25519KeyPair::from_pkcs8_maybe_unchecked(bytes) {
157 return Ok(kp);
158 }
159
160 let seed = auths_crypto::parse_ed25519_seed(bytes)
161 .map_err(|e| IdentityError::Pkcs8DecodeError(format!("Cannot parse key data: {e}")))?;
162 let pkcs8_bytes = encode_seed_as_pkcs8(seed.as_bytes())?;
163 Ed25519KeyPair::from_pkcs8_maybe_unchecked(&pkcs8_bytes)
164 .map_err(|e| IdentityError::RingError(format!("Failed to load Ed25519 keypair: {e}")))
165}
166
167pub fn generate_keypair_with_seed(
168 rng: &SystemRandom,
169) -> Result<(Ed25519KeyPair, Vec<u8>), IdentityError> {
170 let pkcs8_doc = Ed25519KeyPair::generate_pkcs8(rng)?;
171 let pkcs8_bytes = pkcs8_doc.as_ref().to_vec();
172 let keypair = Ed25519KeyPair::from_pkcs8(&pkcs8_bytes)?;
173 Ok((keypair, pkcs8_bytes))
174}