1use std::fs;
7use std::net::{IpAddr, SocketAddr};
8use std::path::Path;
9
10use libp2p_identity::PeerId;
11
12use crate::error::{Error, Result};
13use crate::{Did, Document, EncryptionKey, MaError, SigningKey, VerificationMethod};
14
15#[derive(Debug, Clone)]
22pub struct GeneratedIdentity {
23 pub subject_url: Did,
24 pub document: Document,
25 pub signing_private_key_hex: String,
26 pub encryption_private_key_hex: String,
27}
28
29fn build_identity(ipns: &str) -> Result<GeneratedIdentity> {
30 let sign_url = Did::new_url(ipns, None::<String>).map_err(Error::Validation)?;
31 let enc_url = Did::new_url(ipns, None::<String>).map_err(Error::Validation)?;
32
33 let signing_key = SigningKey::generate(sign_url).map_err(Error::Validation)?;
34 let encryption_key = EncryptionKey::generate(enc_url).map_err(Error::Validation)?;
35
36 build_identity_from_keys(ipns, &signing_key, &encryption_key)
37}
38
39pub(crate) fn build_identity_from_keys(
50 ipns: &str,
51 signing_key: &SigningKey,
52 encryption_key: &EncryptionKey,
53) -> Result<GeneratedIdentity> {
54 let subject_url = Did::new_identity(ipns).map_err(Error::Validation)?;
55
56 let mut document = Document::new(&subject_url, &subject_url);
57
58 let assertion_vm = VerificationMethod::new(
60 subject_url.base_id(),
61 subject_url.base_id(),
62 signing_key.key_type.clone(),
63 "sign",
64 signing_key.public_key_multibase.clone(),
65 )
66 .map_err(Error::Validation)?;
67
68 let key_agreement_vm = VerificationMethod::new(
69 subject_url.base_id(),
70 subject_url.base_id(),
71 encryption_key.key_type.clone(),
72 "enc",
73 encryption_key.public_key_multibase.clone(),
74 )
75 .map_err(Error::Validation)?;
76
77 let assertion_vm_id = assertion_vm.id.clone();
78 document
79 .add_verification_method(assertion_vm.clone())
80 .map_err(Error::Validation)?;
81 document
82 .add_verification_method(key_agreement_vm.clone())
83 .map_err(Error::Validation)?;
84 document.assertion_method = vec![assertion_vm_id];
85 document.key_agreement = vec![key_agreement_vm.id.clone()];
86 document
87 .sign(signing_key, &assertion_vm)
88 .map_err(Error::Validation)?;
89
90 Ok(GeneratedIdentity {
91 subject_url,
92 document,
93 signing_private_key_hex: hex::encode(signing_key.private_key_bytes()),
94 encryption_private_key_hex: hex::encode(encryption_key.private_key_bytes()),
95 })
96}
97
98pub fn ipns_from_secret(secret: [u8; 32]) -> Result<String> {
100 let keypair = libp2p_identity::Keypair::ed25519_from_bytes(secret)
101 .map_err(|_| Error::Validation(MaError::InvalidIdentitySecret))?;
102 let peer_id = PeerId::from_public_key(&keypair.public());
103 Ok(peer_id.to_string())
104}
105
106pub fn generate_identity(ipns: &str) -> Result<GeneratedIdentity> {
108 build_identity(ipns)
109}
110
111pub fn generate_identity_from_secret(secret: [u8; 32]) -> Result<GeneratedIdentity> {
114 let ipns = ipns_from_secret(secret)?;
115 build_identity(&ipns)
116}
117
118pub fn load_secret_key_bytes(path: &Path) -> Result<Option<[u8; 32]>> {
124 if !path.exists() {
125 return Ok(None);
126 }
127
128 let bytes = fs::read(path).map_err(|e| Error::SecretKey(e.to_string()))?;
129 let key_bytes: [u8; 32] = bytes
130 .as_slice()
131 .try_into()
132 .map_err(|_| Error::SecretKey(format!("invalid key file length in {}", path.display())))?;
133
134 Ok(Some(key_bytes))
135}
136
137pub fn generate_secret_key_file(path: &Path) -> Result<[u8; 32]> {
143 if path.exists() {
144 return Err(Error::SecretKey(format!(
145 "secret key already exists at {}",
146 path.display()
147 )));
148 }
149
150 let mut key_bytes = [0u8; 32];
151 use rand::RngCore;
152 rand::rngs::OsRng.fill_bytes(&mut key_bytes);
153
154 if let Some(parent) = path.parent() {
156 fs::create_dir_all(parent).map_err(|e| {
157 Error::SecretKey(format!("failed to create dir {}: {}", parent.display(), e))
158 })?;
159 }
160
161 fs::write(path, key_bytes)
162 .map_err(|e| Error::SecretKey(format!("failed to write {}: {}", path.display(), e)))?;
163
164 #[cfg(unix)]
166 {
167 use std::os::unix::fs::PermissionsExt;
168 let _ = fs::set_permissions(path, fs::Permissions::from_mode(0o400));
169 }
170
171 Ok(key_bytes)
172}
173
174pub fn socket_addr_to_multiaddr(addr: &SocketAddr) -> String {
176 match addr.ip() {
177 IpAddr::V4(ip) => format!("/ip4/{}/udp/{}/quic-v1", ip, addr.port()),
178 IpAddr::V6(ip) => format!("/ip6/{}/udp/{}/quic-v1", ip, addr.port()),
179 }
180}
181
182#[cfg(test)]
183mod tests {
184 use super::*;
185 use std::net::{Ipv4Addr, Ipv6Addr};
186 use std::path::PathBuf;
187
188 fn test_tmp_file(name: &str) -> PathBuf {
189 let root = PathBuf::from(env!("CARGO_MANIFEST_DIR"))
190 .join("tmp")
191 .join("identity-tests");
192 fs::create_dir_all(&root).expect("failed creating test tmp directory");
193 root.join(name)
194 }
195
196 #[test]
197 fn multiaddr_ipv4() {
198 let addr = SocketAddr::new(IpAddr::V4(Ipv4Addr::LOCALHOST), 4433);
199 assert_eq!(
200 socket_addr_to_multiaddr(&addr),
201 "/ip4/127.0.0.1/udp/4433/quic-v1"
202 );
203 }
204
205 #[test]
206 fn multiaddr_ipv6() {
207 let addr = SocketAddr::new(IpAddr::V6(Ipv6Addr::LOCALHOST), 5555);
208 assert_eq!(socket_addr_to_multiaddr(&addr), "/ip6/::1/udp/5555/quic-v1");
209 }
210
211 #[test]
212 fn load_missing_returns_none() {
213 let path = test_tmp_file("nonexistent-key");
214 let _ = fs::remove_file(&path);
215 assert!(load_secret_key_bytes(&path).unwrap().is_none());
216 }
217
218 #[test]
219 fn generate_and_load_round_trip() {
220 let path = test_tmp_file("round-trip-key");
221 let _ = fs::remove_file(&path);
222
223 let generated = generate_secret_key_file(&path).unwrap();
224 let loaded = load_secret_key_bytes(&path).unwrap().unwrap();
225 assert_eq!(generated, loaded);
226
227 let _ = fs::remove_file(&path);
229 }
230
231 #[test]
232 fn generate_refuses_overwrite() {
233 let path = test_tmp_file("no-overwrite-key");
234 let _ = fs::remove_file(&path);
235
236 generate_secret_key_file(&path).unwrap();
237 let err = generate_secret_key_file(&path).unwrap_err();
238 assert!(matches!(err, crate::error::Error::SecretKey(_)));
239
240 let _ = fs::remove_file(&path);
242 }
243}