tangled
alpha
login
or
join now
sans-self.org
/
opake.app
0
fork
atom
An encrypted personal cloud built on the AT Protocol.
0
fork
atom
overview
issues
pulls
pipelines
Merge branch 'feature/require-directory-on-upload'
sans-self.org
2 weeks ago
7b92b71f
e059ac89
0/0
Waiting for spindle ...
+55
-46
4 changed files
expand all
collapse all
unified
split
CHANGELOG.md
crates
opake-cli
src
commands
mkdir.rs
mod.rs
upload.rs
+3
-1
CHANGELOG.md
reviewed
···
9
9
### Security
10
10
- Fix ContentKey Debug impl to redact secret bytes [#49](https://issues.opake.app/issues/49.html)
11
11
- Add file permission hardening for sensitive config and key files [#8](https://issues.opake.app/issues/8.html)
12
12
-
- Remove bearer token authentication fallback from AppView [#26](https://issues.opake.app/issues/26.html)
12
12
+
- Remove bearer token authentication fallback from AppView [#26](https://issues.opake.app/issues/26.html)s
13
13
14
14
### Added
15
15
- Add metadata CLI command for rename, tag, and description management [#190](https://issues.opake.app/issues/190.html)
···
62
62
- Update login command to read password from stdin [#112](https://issues.opake.app/issues/112.html)
63
63
64
64
### Fixed
65
65
+
- Require directory on upload, default to root when --dir is omitted [#192](https://issues.opake.app/issues/192.html)
65
66
- Fix bugs found during black-box integration testing of sharing workflow [#70](https://issues.opake.app/issues/70.html)
66
67
- Fix base64 padding mismatch when decoding PDS $bytes fields [#68](https://issues.opake.app/issues/68.html)
67
68
- Fix missing HTTP status checks in XRPC client [#104](https://issues.opake.app/issues/104.html)
68
69
69
70
### Changed
71
71
+
- Add metadata CLI command with tag and description subcommands [#190](https://issues.opake.app/issues/190.html)
70
72
- Encrypt directory metadata (add encryption envelope to directory records) [#189](https://issues.opake.app/issues/189.html)
71
73
- Encrypt keyring and grant metadata [#188](https://issues.opake.app/issues/188.html)
72
74
- Encrypt document metadata (name, mimeType, tags, description) [#187](https://issues.opake.app/issues/187.html)
+2
-35
crates/opake-cli/src/commands/mkdir.rs
reviewed
···
2
2
use chrono::Utc;
3
3
use clap::Args;
4
4
use opake_core::client::Session;
5
5
-
use opake_core::crypto::{self, DirectoryMetadata, OsRng};
5
5
+
use opake_core::crypto::OsRng;
6
6
use opake_core::directories;
7
7
-
use opake_core::records::{DirectEncryption, Encryption, EncryptionEnvelope};
8
7
9
9
-
use crate::commands::Execute;
8
8
+
use crate::commands::{encrypt_directory, Execute};
10
9
use crate::identity;
11
10
use crate::session::{self, CommandContext};
12
11
···
15
14
pub struct MkdirCommand {
16
15
/// Name for the directory
17
16
name: String,
18
18
-
}
19
19
-
20
20
-
/// Build a direct encryption envelope for a directory.
21
21
-
///
22
22
-
/// Generates a fresh content key, encrypts the metadata with it, and wraps
23
23
-
/// the key to the owner's public key. Returns the encryption enum and
24
24
-
/// encrypted metadata for the directory record.
25
25
-
fn encrypt_directory(
26
26
-
name: &str,
27
27
-
owner_did: &str,
28
28
-
owner_pubkey: &crypto::X25519PublicKey,
29
29
-
rng: &mut (impl crypto::CryptoRng + crypto::RngCore),
30
30
-
) -> Result<(Encryption, opake_core::records::EncryptedMetadata)> {
31
31
-
let content_key = crypto::generate_content_key(rng);
32
32
-
33
33
-
let metadata = DirectoryMetadata {
34
34
-
name: name.into(),
35
35
-
description: None,
36
36
-
};
37
37
-
let encrypted_metadata = crypto::encrypt_metadata(&content_key, &metadata, rng)?;
38
38
-
39
39
-
let wrapped_key = crypto::wrap_key(&content_key, owner_pubkey, owner_did, rng)?;
40
40
-
41
41
-
let encryption = Encryption::Direct(DirectEncryption {
42
42
-
envelope: EncryptionEnvelope {
43
43
-
algo: "aes-256-gcm".into(),
44
44
-
nonce: opake_core::records::AtBytes::from_raw(&[0u8; 12]),
45
45
-
keys: vec![wrapped_key],
46
46
-
},
47
47
-
});
48
48
-
49
49
-
Ok((encryption, encrypted_metadata))
50
17
}
51
18
52
19
impl Execute for MkdirCommand {
+35
crates/opake-cli/src/commands/mod.rs
reviewed
···
21
21
22
22
use anyhow::Result;
23
23
use opake_core::client::Session;
24
24
+
use opake_core::crypto::{self, DirectoryMetadata};
25
25
+
use opake_core::records::{
26
26
+
AtBytes, DirectEncryption, EncryptedMetadata, Encryption, EncryptionEnvelope,
27
27
+
};
24
28
25
29
use crate::session::CommandContext;
26
30
···
30
34
ctx: &CommandContext,
31
35
) -> impl std::future::Future<Output = Result<Option<Session>>>;
32
36
}
37
37
+
38
38
+
/// Build a direct encryption envelope for a directory.
39
39
+
///
40
40
+
/// Generates a fresh content key, encrypts the metadata with it, and wraps
41
41
+
/// the key to the owner's public key.
42
42
+
pub fn encrypt_directory(
43
43
+
name: &str,
44
44
+
owner_did: &str,
45
45
+
owner_pubkey: &crypto::X25519PublicKey,
46
46
+
rng: &mut (impl crypto::CryptoRng + crypto::RngCore),
47
47
+
) -> Result<(Encryption, EncryptedMetadata)> {
48
48
+
let content_key = crypto::generate_content_key(rng);
49
49
+
50
50
+
let metadata = DirectoryMetadata {
51
51
+
name: name.into(),
52
52
+
description: None,
53
53
+
};
54
54
+
let encrypted_metadata = crypto::encrypt_metadata(&content_key, &metadata, rng)?;
55
55
+
56
56
+
let wrapped_key = crypto::wrap_key(&content_key, owner_pubkey, owner_did, rng)?;
57
57
+
58
58
+
let encryption = Encryption::Direct(DirectEncryption {
59
59
+
envelope: EncryptionEnvelope {
60
60
+
algo: "aes-256-gcm".into(),
61
61
+
nonce: AtBytes::from_raw(&[0u8; 12]),
62
62
+
keys: vec![wrapped_key],
63
63
+
},
64
64
+
});
65
65
+
66
66
+
Ok((encryption, encrypted_metadata))
67
67
+
}
+15
-10
crates/opake-cli/src/commands/upload.rs
reviewed
···
12
12
13
13
use opake_core::client::Session;
14
14
15
15
-
use crate::commands::Execute;
15
15
+
use crate::commands::{encrypt_directory, Execute};
16
16
use crate::identity;
17
17
use crate::keyring_store;
18
18
use crate::session::{self, CommandContext};
···
31
31
#[arg(long)]
32
32
description: Option<String>,
33
33
34
34
-
/// Place the uploaded document into a directory
34
34
+
/// Directory to place the document in (defaults to root "/")
35
35
#[arg(long)]
36
36
dir: Option<String>,
37
37
}
···
39
39
impl Execute for UploadCommand {
40
40
async fn execute(self, ctx: &CommandContext) -> Result<Option<Session>> {
41
41
let mut client = session::load_client(&ctx.storage, &ctx.did)?;
42
42
+
let id = identity::load_identity(&ctx.storage, &ctx.did)?;
42
43
43
44
let plaintext =
44
45
fs::read(&self.path).context(format!("failed to read {}", self.path.display()))?;
···
56
57
let now = Utc::now().to_rfc3339();
57
58
58
59
let uri = if let Some(keyring_name) = &self.keyring {
59
59
-
let id = identity::load_identity(&ctx.storage, &ctx.did)?;
60
60
let private_key = id.private_key_bytes()?;
61
61
let entry =
62
62
keyrings::resolve_keyring_uri(&mut client, keyring_name, &id.did, &private_key)
···
82
82
83
83
documents::encrypt_and_upload_keyring(&mut client, ¶ms, &mut OsRng).await?
84
84
} else {
85
85
-
let id = identity::load_identity(&ctx.storage, &ctx.did)?;
86
85
let owner_pubkey = id.public_key_bytes()?;
87
86
88
87
let params = UploadParams {
···
98
97
documents::encrypt_and_upload(&mut client, ¶ms, &mut OsRng).await?
99
98
};
100
99
101
101
-
if let Some(dir_path) = &self.dir {
102
102
-
let id = identity::load_identity(&ctx.storage, &ctx.did)?;
100
100
+
let (directory_uri, dir_label) = if let Some(dir_path) = &self.dir {
103
101
let private_key = id.private_key_bytes()?;
104
102
let mut tree = DirectoryTree::load(&mut client).await?;
105
103
tree.decrypt_names(&ctx.did, &private_key);
···
109
107
anyhow::bail!("{dir_path:?} is not a directory");
110
108
}
111
109
112
112
-
directories::add_entry(&mut client, &resolved.uri, &uri, &now).await?;
113
113
-
println!("{} → {} (in {})", filename, uri, dir_path);
110
110
+
(resolved.uri, dir_path.clone())
114
111
} else {
115
115
-
println!("{} → {}", filename, uri);
116
116
-
}
112
112
+
let pubkey = id.public_key_bytes()?;
113
113
+
let (root_enc, root_meta) = encrypt_directory("/", &ctx.did, &pubkey, &mut OsRng)?;
114
114
+
let root_uri =
115
115
+
directories::get_or_create_root(&mut client, &ctx.did, root_enc, root_meta, &now)
116
116
+
.await?;
117
117
+
(root_uri, "/".into())
118
118
+
};
119
119
+
120
120
+
directories::add_entry(&mut client, &directory_uri, &uri, &now).await?;
121
121
+
println!("{} → {} (in {})", filename, uri, dir_label);
117
122
118
123
Ok(session::refreshed_session(&client))
119
124
}