fix: encode NIP-17 file key and nonce as hex
Amethyst parses decryption-key and decryption-nonce as hex, so base64 keys were unreadable by the only implementation of kind 15 file messages. Also accept the 16 bytes nonce Amethyst sends, while still emitting the standard 12 bytes. Replaces the now unused base64 dependency with data-encoding.
This commit is contained in:
@@ -26,7 +26,7 @@ mime_guess = "2.0.4"
|
||||
|
||||
aes-gcm.workspace = true
|
||||
sha2.workspace = true
|
||||
base64.workspace = true
|
||||
data-encoding.workspace = true
|
||||
|
||||
[target.'cfg(target_arch = "wasm32")'.dependencies]
|
||||
nostr-memory.workspace = true
|
||||
|
||||
+45
-29
@@ -1,11 +1,11 @@
|
||||
use std::path::PathBuf;
|
||||
|
||||
use aes_gcm::aead::consts::U12;
|
||||
use aes_gcm::aead::consts::{U12, U16};
|
||||
use aes_gcm::aead::{Aead, AeadCore, KeyInit, OsRng};
|
||||
use aes_gcm::{Aes256Gcm, Nonce};
|
||||
use aes_gcm::aes::Aes256;
|
||||
use aes_gcm::{Aes256Gcm, AesGcm, Nonce};
|
||||
use anyhow::{Error, anyhow, bail};
|
||||
use base64::Engine as _;
|
||||
use base64::engine::general_purpose::{STANDARD, STANDARD_NO_PAD, URL_SAFE, URL_SAFE_NO_PAD};
|
||||
use data_encoding::HEXLOWER;
|
||||
use futures::AsyncReadExt;
|
||||
use gpui::http_client::AsyncBody;
|
||||
use gpui::{AsyncApp, SharedString};
|
||||
@@ -130,20 +130,34 @@ pub fn encrypt(data: &[u8]) -> Result<EncryptedFile, Error> {
|
||||
|
||||
Ok(EncryptedFile {
|
||||
data,
|
||||
key: STANDARD.encode(key.as_slice()),
|
||||
nonce: STANDARD.encode(nonce.as_slice()),
|
||||
key: HEXLOWER.encode(key.as_slice()),
|
||||
nonce: HEXLOWER.encode(nonce.as_slice()),
|
||||
})
|
||||
}
|
||||
|
||||
pub fn decrypt(data: &[u8], key: &str, nonce: &str) -> Result<Vec<u8>, Error> {
|
||||
let key = decode(key, 32, "decryption key")?;
|
||||
let nonce = decode(nonce, 12, "decryption nonce")?;
|
||||
let key = decode(key, "decryption key")?;
|
||||
let nonce = decode(nonce, "decryption nonce")?;
|
||||
|
||||
let cipher = Aes256Gcm::new_from_slice(&key).map_err(|_| anyhow!("Invalid decryption key"))?;
|
||||
if key.len() != 32 {
|
||||
bail!(
|
||||
"Invalid decryption key length: expected 32 bytes, got {}",
|
||||
key.len()
|
||||
);
|
||||
}
|
||||
|
||||
cipher
|
||||
.decrypt(Nonce::<U12>::from_slice(&nonce), data)
|
||||
.map_err(|_| anyhow!("Failed to decrypt file"))
|
||||
// Amethyst uses 16 bytes nonces, everything else uses the standard 12
|
||||
match nonce.len() {
|
||||
12 => Aes256Gcm::new_from_slice(&key)
|
||||
.map_err(|_| anyhow!("Invalid decryption key"))?
|
||||
.decrypt(Nonce::<U12>::from_slice(&nonce), data)
|
||||
.map_err(|_| anyhow!("Failed to decrypt file")),
|
||||
16 => AesGcm::<Aes256, U16>::new_from_slice(&key)
|
||||
.map_err(|_| anyhow!("Invalid decryption key"))?
|
||||
.decrypt(Nonce::<U16>::from_slice(&nonce), data)
|
||||
.map_err(|_| anyhow!("Failed to decrypt file")),
|
||||
len => bail!("Unsupported decryption nonce length: {len} bytes"),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn sha256_hex(data: &[u8]) -> String {
|
||||
@@ -168,6 +182,7 @@ pub async fn upload_encrypted(
|
||||
let sha256 = Some(sha256_hex(&encrypted.data));
|
||||
let original_sha256 = Some(sha256_hex(&data));
|
||||
let size = Some(encrypted.data.len() as u64);
|
||||
let base_url = server.to_string();
|
||||
let keys = Keys::generate();
|
||||
let client = BlossomClient::new(server);
|
||||
|
||||
@@ -179,7 +194,20 @@ pub async fn upload_encrypted(
|
||||
None,
|
||||
Some(&keys),
|
||||
)
|
||||
.await?;
|
||||
.await
|
||||
.map_err(|e| {
|
||||
let message = e.to_string();
|
||||
|
||||
if !message.contains("415") {
|
||||
return anyhow!(message);
|
||||
}
|
||||
|
||||
anyhow!(
|
||||
"{base_url} rejected the encrypted file. Encrypted attachments are uploaded as
|
||||
opaque data, which this file server does not accept. Choose a different file
|
||||
server in the settings."
|
||||
)
|
||||
})?;
|
||||
|
||||
Ok::<Url, Error>(blob.url)
|
||||
})
|
||||
@@ -311,20 +339,8 @@ fn parse_dim(value: &str) -> Option<(u32, u32)> {
|
||||
Some((width.parse().ok()?, height.parse().ok()?))
|
||||
}
|
||||
|
||||
fn decode(value: &str, expected_len: usize, label: &str) -> Result<Vec<u8>, Error> {
|
||||
let decoded = STANDARD
|
||||
.decode(value)
|
||||
.or_else(|_| STANDARD_NO_PAD.decode(value))
|
||||
.or_else(|_| URL_SAFE.decode(value))
|
||||
.or_else(|_| URL_SAFE_NO_PAD.decode(value))
|
||||
.map_err(|_| anyhow!("Invalid {label} encoding"))?;
|
||||
|
||||
if decoded.len() != expected_len {
|
||||
bail!(
|
||||
"Invalid {label} length: expected {expected_len} bytes, got {}",
|
||||
decoded.len()
|
||||
);
|
||||
}
|
||||
|
||||
Ok(decoded)
|
||||
fn decode(value: &str, label: &str) -> Result<Vec<u8>, Error> {
|
||||
HEXLOWER
|
||||
.decode(value.to_ascii_lowercase().as_bytes())
|
||||
.map_err(|_| anyhow!("Invalid {label} encoding"))
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user