fix: restore green source validation baseline
This commit is contained in:
@@ -205,14 +205,22 @@ impl IdentityManager {
|
||||
nsec: &str,
|
||||
expected_npub: &str,
|
||||
) -> Result<IdentityRecord> {
|
||||
anyhow::ensure!(!name.trim().is_empty() && name.len() <= 100, "Invalid identity name");
|
||||
anyhow::ensure!(nsec.starts_with("nsec1") && nsec.len() == 63, "Enter a plain nsec owner key");
|
||||
let secret = nostr_sdk::SecretKey::parse(nsec)
|
||||
.map_err(|_| anyhow::anyhow!("Invalid owner key"))?;
|
||||
anyhow::ensure!(
|
||||
!name.trim().is_empty() && name.len() <= 100,
|
||||
"Invalid identity name"
|
||||
);
|
||||
anyhow::ensure!(
|
||||
nsec.starts_with("nsec1") && nsec.len() == 63,
|
||||
"Enter a plain nsec owner key"
|
||||
);
|
||||
let secret =
|
||||
nostr_sdk::SecretKey::parse(nsec).map_err(|_| anyhow::anyhow!("Invalid owner key"))?;
|
||||
let keys = nostr_sdk::Keys::new(secret);
|
||||
let nostr_pubkey = keys.public_key().to_hex();
|
||||
anyhow::ensure!(keys.public_key().to_bech32()? == expected_npub,
|
||||
"Owner key does not match this website");
|
||||
anyhow::ensure!(
|
||||
keys.public_key().to_bech32()? == expected_npub,
|
||||
"Owner key does not match this website"
|
||||
);
|
||||
|
||||
// Serializes imports only; mature creation/signing paths are untouched.
|
||||
static IMPORT_LOCK: tokio::sync::Mutex<()> = tokio::sync::Mutex::const_new(());
|
||||
@@ -260,7 +268,8 @@ impl IdentityManager {
|
||||
// Atomic publication, and unlike rename this cannot replace a file.
|
||||
fs::hard_link(&staging, &destination).await?;
|
||||
Ok(())
|
||||
}.await;
|
||||
}
|
||||
.await;
|
||||
let _ = fs::remove_file(&staging).await;
|
||||
write_result.context("Could not save imported identity")?;
|
||||
self.get(&id).await
|
||||
@@ -974,23 +983,53 @@ mod tests {
|
||||
async fn import_nostr_preserves_identities_and_rejects_mismatches() {
|
||||
let dir = tempdir().unwrap();
|
||||
let manager = IdentityManager::new(dir.path()).await.unwrap();
|
||||
let original = manager.create("Personal".into(), IdentityPurpose::Personal).await.unwrap();
|
||||
let original = manager
|
||||
.create("Personal".into(), IdentityPurpose::Personal)
|
||||
.await
|
||||
.unwrap();
|
||||
let keys = nostr_sdk::Keys::generate();
|
||||
let nsec = keys.secret_key().to_bech32().unwrap();
|
||||
let npub = keys.public_key().to_bech32().unwrap();
|
||||
assert!(manager.import_nostr("Wrong".into(), &nsec, "npub1wrong").await.is_err());
|
||||
assert!(manager
|
||||
.import_nostr("Wrong".into(), &nsec, "npub1wrong")
|
||||
.await
|
||||
.is_err());
|
||||
assert_eq!(manager.list().await.unwrap().0.len(), 1);
|
||||
let imported = manager.import_nostr("Website".into(), &nsec, &npub).await.unwrap();
|
||||
let imported = manager
|
||||
.import_nostr("Website".into(), &nsec, &npub)
|
||||
.await
|
||||
.unwrap();
|
||||
assert_eq!(imported.nostr_npub.as_deref(), Some(npub.as_str()));
|
||||
assert_eq!(manager.import_nostr("Again".into(), &nsec, &npub).await.unwrap().id, imported.id);
|
||||
assert_eq!(
|
||||
manager
|
||||
.import_nostr("Again".into(), &nsec, &npub)
|
||||
.await
|
||||
.unwrap()
|
||||
.id,
|
||||
imported.id
|
||||
);
|
||||
let (records, default) = manager.list().await.unwrap();
|
||||
assert_eq!(records.len(), 2);
|
||||
assert_eq!(default.as_deref(), Some(original.id.as_str()));
|
||||
assert_eq!(manager.get(&original.id).await.unwrap().nostr_pubkey, original.nostr_pubkey);
|
||||
assert_eq!(manager.export_keys(&imported.id).await.unwrap()["nostr_nsec"], nsec);
|
||||
#[cfg(unix)] {
|
||||
assert_eq!(
|
||||
manager.get(&original.id).await.unwrap().nostr_pubkey,
|
||||
original.nostr_pubkey
|
||||
);
|
||||
assert_eq!(
|
||||
manager.export_keys(&imported.id).await.unwrap()["nostr_nsec"],
|
||||
nsec
|
||||
);
|
||||
#[cfg(unix)]
|
||||
{
|
||||
use std::os::unix::fs::PermissionsExt;
|
||||
let mode = std::fs::metadata(dir.path().join("identities").join(format!("{}.json", imported.id))).unwrap().permissions().mode();
|
||||
let mode = std::fs::metadata(
|
||||
dir.path()
|
||||
.join("identities")
|
||||
.join(format!("{}.json", imported.id)),
|
||||
)
|
||||
.unwrap()
|
||||
.permissions()
|
||||
.mode();
|
||||
assert_eq!(mode & 0o777, 0o600);
|
||||
}
|
||||
}
|
||||
@@ -1012,14 +1051,24 @@ mod tests {
|
||||
assert_eq!(records.len(), 1);
|
||||
assert!(default.is_none());
|
||||
let hash = [7u8; 32];
|
||||
let signature = manager.nostr_sign(&first.id, &hex::encode(hash)).await.unwrap();
|
||||
let signature = manager
|
||||
.nostr_sign(&first.id, &hex::encode(hash))
|
||||
.await
|
||||
.unwrap();
|
||||
let signature: nostr_sdk::secp256k1::schnorr::Signature = signature.parse().unwrap();
|
||||
let pubkey: nostr_sdk::secp256k1::XOnlyPublicKey = keys.public_key().to_hex().parse().unwrap();
|
||||
nostr_sdk::secp256k1::Secp256k1::verification_only().verify_schnorr(
|
||||
&signature, &nostr_sdk::secp256k1::Message::from_digest(hash), &pubkey,
|
||||
).unwrap();
|
||||
let pubkey: nostr_sdk::secp256k1::XOnlyPublicKey =
|
||||
keys.public_key().to_hex().parse().unwrap();
|
||||
nostr_sdk::secp256k1::Secp256k1::verification_only()
|
||||
.verify_schnorr(
|
||||
&signature,
|
||||
&nostr_sdk::secp256k1::Message::from_digest(hash),
|
||||
&pubkey,
|
||||
)
|
||||
.unwrap();
|
||||
let entries = std::fs::read_dir(dir.path().join("identities")).unwrap();
|
||||
assert!(entries.map(|entry| entry.unwrap().file_name()).all(|name| !name.to_string_lossy().ends_with(".tmp")));
|
||||
assert!(entries
|
||||
.map(|entry| entry.unwrap().file_name())
|
||||
.all(|name| !name.to_string_lossy().ends_with(".tmp")));
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
|
||||
Reference in New Issue
Block a user