Skip to main content

auths_sdk/
keys.rs

1//! Key import and management operations.
2//!
3//! Provides SDK-level key management functions that wrap `auths-core` crypto
4//! primitives. These functions are the canonical entry point for key operations
5//! — the CLI is a thin wrapper that reads files and calls these.
6
7use auths_core::crypto::signer::encrypt_keypair;
8use auths_core::crypto::ssh::build_ed25519_pkcs8_v2_from_seed;
9use auths_core::storage::keychain::{KeyAlias, KeyStorage};
10use auths_crypto::SecureSeed;
11use auths_verifier::IdentityDID;
12use thiserror::Error;
13use zeroize::Zeroizing;
14
15/// Errors from key import operations.
16#[derive(Debug, Error)]
17#[non_exhaustive]
18pub enum KeyImportError {
19    /// The seed is not exactly 32 bytes.
20    #[error("seed must be exactly 32 bytes, got {0}")]
21    InvalidSeedLength(usize),
22
23    /// The alias string is empty.
24    #[error("key alias cannot be empty")]
25    EmptyAlias,
26
27    /// PKCS#8 DER encoding failed.
28    #[error("failed to generate PKCS#8 from seed: {0}")]
29    Pkcs8Generation(String),
30
31    /// Encryption of the private key failed.
32    #[error("failed to encrypt private key: {0}")]
33    Encryption(String),
34
35    /// Storing the encrypted key in the keychain failed.
36    #[error("failed to store key in keychain: {0}")]
37    KeychainStore(String),
38}
39
40/// Result of a successful key import.
41#[derive(Debug, Clone)]
42pub struct KeyImportResult {
43    /// The 32-byte Ed25519 public key derived from the seed.
44    pub public_key: [u8; 32],
45    /// The alias under which the key was stored.
46    pub alias: String,
47}
48
49/// Imports an Ed25519 key from a raw 32-byte seed into the keychain.
50///
51/// Generates PKCS#8 v2 DER from the seed, encrypts with the passphrase, and
52/// stores in the provided keychain under the given alias associated with the
53/// controller DID. No file I/O, no terminal interaction.
54///
55/// Args:
56/// * `seed`: The raw 32-byte Ed25519 seed, wrapped in `Zeroizing`.
57/// * `passphrase`: The encryption passphrase, wrapped in `Zeroizing`.
58/// * `alias`: The local keychain alias for this key.
59/// * `controller_did`: The identity DID this key is associated with.
60/// * `keychain`: The keychain backend to store the encrypted key.
61///
62/// Usage:
63/// ```ignore
64/// let result = import_seed(
65///     &seed, &passphrase, "my_key",
66///     &IdentityDID::new("did:keri:EXq5abc"),
67///     keychain.as_ref(),
68/// )?;
69/// ```
70pub fn import_seed(
71    seed: &Zeroizing<[u8; 32]>,
72    passphrase: &Zeroizing<String>,
73    alias: &str,
74    controller_did: &IdentityDID,
75    keychain: &dyn KeyStorage,
76) -> Result<KeyImportResult, KeyImportError> {
77    if alias.trim().is_empty() {
78        return Err(KeyImportError::EmptyAlias);
79    }
80
81    let secure_seed = SecureSeed::new(**seed);
82
83    let pkcs8_bytes = build_ed25519_pkcs8_v2_from_seed(&secure_seed)
84        .map_err(|e| KeyImportError::Pkcs8Generation(e.to_string()))?;
85
86    let encrypted = encrypt_keypair(&pkcs8_bytes, passphrase)
87        .map_err(|e| KeyImportError::Encryption(e.to_string()))?;
88
89    keychain
90        .store_key(&KeyAlias::new_unchecked(alias), controller_did, &encrypted)
91        .map_err(|e| KeyImportError::KeychainStore(e.to_string()))?;
92
93    let public_key =
94        auths_core::crypto::provider_bridge::ed25519_public_key_from_seed_sync(&secure_seed)
95            .map_err(|e| {
96                KeyImportError::Pkcs8Generation(format!("failed to derive public key: {e}"))
97            })?;
98
99    Ok(KeyImportResult {
100        public_key,
101        alias: alias.to_string(),
102    })
103}