cortex-ai-memory 2.3.0__tar.gz → 2.4.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {cortex_ai_memory-2.3.0 → cortex_ai_memory-2.4.0}/Cargo.lock +6 -7
- {cortex_ai_memory-2.3.0 → cortex_ai_memory-2.4.0}/PKG-INFO +1 -1
- {cortex_ai_memory-2.3.0 → cortex_ai_memory-2.4.0}/cortex-core/Cargo.toml +2 -1
- {cortex_ai_memory-2.3.0 → cortex_ai_memory-2.4.0}/cortex-core/src/export.rs +7 -0
- {cortex_ai_memory-2.3.0 → cortex_ai_memory-2.4.0}/cortex-core/src/lib.rs +22 -5
- {cortex_ai_memory-2.3.0 → cortex_ai_memory-2.4.0}/cortex-core/src/sync/crypto.rs +78 -0
- cortex_ai_memory-2.4.0/cortex-core/src/sync/mod.rs +1211 -0
- {cortex_ai_memory-2.3.0 → cortex_ai_memory-2.4.0}/cortex-core/src/sync/oplog.rs +13 -4
- cortex_ai_memory-2.4.0/cortex-core/src/sync/snapshot.rs +717 -0
- {cortex_ai_memory-2.3.0 → cortex_ai_memory-2.4.0}/cortex-core/src/sync/state.rs +125 -0
- cortex_ai_memory-2.4.0/cortex-core/tests/test_reserved_namespace.rs +112 -0
- {cortex_ai_memory-2.3.0 → cortex_ai_memory-2.4.0}/cortex-core/tests/test_sync.rs +1 -1
- cortex_ai_memory-2.4.0/cortex-core/tests/test_sync_hardening.rs +554 -0
- {cortex_ai_memory-2.3.0 → cortex_ai_memory-2.4.0}/cortex-python/Cargo.toml +2 -1
- {cortex_ai_memory-2.3.0 → cortex_ai_memory-2.4.0}/pyproject.toml +1 -1
- cortex_ai_memory-2.3.0/cortex-core/src/sync/mod.rs +0 -687
- cortex_ai_memory-2.3.0/cortex-core/src/sync/snapshot.rs +0 -361
- cortex_ai_memory-2.3.0/cortex-core/tests/test_reserved_namespace.rs +0 -35
- {cortex_ai_memory-2.3.0 → cortex_ai_memory-2.4.0}/Cargo.toml +0 -0
- {cortex_ai_memory-2.3.0 → cortex_ai_memory-2.4.0}/README.md +0 -0
- {cortex_ai_memory-2.3.0 → cortex_ai_memory-2.4.0}/cortex-core/benches/comparison_bench.rs +0 -0
- {cortex_ai_memory-2.3.0 → cortex_ai_memory-2.4.0}/cortex-core/benches/memory_index_bench.rs +0 -0
- {cortex_ai_memory-2.3.0 → cortex_ai_memory-2.4.0}/cortex-core/src/belief.rs +0 -0
- {cortex_ai_memory-2.3.0 → cortex_ai_memory-2.4.0}/cortex-core/src/compression.rs +0 -0
- {cortex_ai_memory-2.3.0 → cortex_ai_memory-2.4.0}/cortex-core/src/consolidation.rs +0 -0
- {cortex_ai_memory-2.3.0 → cortex_ai_memory-2.4.0}/cortex-core/src/context.rs +0 -0
- {cortex_ai_memory-2.3.0 → cortex_ai_memory-2.4.0}/cortex-core/src/embedder.rs +0 -0
- {cortex_ai_memory-2.3.0 → cortex_ai_memory-2.4.0}/cortex-core/src/episode.rs +0 -0
- {cortex_ai_memory-2.3.0 → cortex_ai_memory-2.4.0}/cortex-core/src/events.rs +0 -0
- {cortex_ai_memory-2.3.0 → cortex_ai_memory-2.4.0}/cortex-core/src/inference.rs +0 -0
- {cortex_ai_memory-2.3.0 → cortex_ai_memory-2.4.0}/cortex-core/src/people.rs +0 -0
- {cortex_ai_memory-2.3.0 → cortex_ai_memory-2.4.0}/cortex-core/src/plugin.rs +0 -0
- {cortex_ai_memory-2.3.0 → cortex_ai_memory-2.4.0}/cortex-core/src/plugin_manager.rs +0 -0
- {cortex_ai_memory-2.3.0 → cortex_ai_memory-2.4.0}/cortex-core/src/plugins/mod.rs +0 -0
- {cortex_ai_memory-2.3.0 → cortex_ai_memory-2.4.0}/cortex-core/src/plugins/tag_classifier.rs +0 -0
- {cortex_ai_memory-2.3.0 → cortex_ai_memory-2.4.0}/cortex-core/src/procedural.rs +0 -0
- {cortex_ai_memory-2.3.0 → cortex_ai_memory-2.4.0}/cortex-core/src/relationship.rs +0 -0
- {cortex_ai_memory-2.3.0 → cortex_ai_memory-2.4.0}/cortex-core/src/retrieval.rs +0 -0
- {cortex_ai_memory-2.3.0 → cortex_ai_memory-2.4.0}/cortex-core/src/semantic.rs +0 -0
- {cortex_ai_memory-2.3.0 → cortex_ai_memory-2.4.0}/cortex-core/src/storage/memory_index.rs +0 -0
- {cortex_ai_memory-2.3.0 → cortex_ai_memory-2.4.0}/cortex-core/src/storage/mod.rs +0 -0
- {cortex_ai_memory-2.3.0 → cortex_ai_memory-2.4.0}/cortex-core/src/storage/sqlite.rs +0 -0
- {cortex_ai_memory-2.3.0 → cortex_ai_memory-2.4.0}/cortex-core/src/storage/traits.rs +0 -0
- {cortex_ai_memory-2.3.0 → cortex_ai_memory-2.4.0}/cortex-core/src/sync/hlc.rs +0 -0
- {cortex_ai_memory-2.3.0 → cortex_ai_memory-2.4.0}/cortex-core/src/sync/merge.rs +0 -0
- {cortex_ai_memory-2.3.0 → cortex_ai_memory-2.4.0}/cortex-core/src/sync/provider.rs +0 -0
- {cortex_ai_memory-2.3.0 → cortex_ai_memory-2.4.0}/cortex-core/src/sync/secret.rs +0 -0
- {cortex_ai_memory-2.3.0 → cortex_ai_memory-2.4.0}/cortex-core/src/sync/watcher.rs +0 -0
- {cortex_ai_memory-2.3.0 → cortex_ai_memory-2.4.0}/cortex-core/src/types.rs +0 -0
- {cortex_ai_memory-2.3.0 → cortex_ai_memory-2.4.0}/cortex-core/src/working.rs +0 -0
- {cortex_ai_memory-2.3.0 → cortex_ai_memory-2.4.0}/cortex-core/tests/test_belief.rs +0 -0
- {cortex_ai_memory-2.3.0 → cortex_ai_memory-2.4.0}/cortex-core/tests/test_cache_and_perf.rs +0 -0
- {cortex_ai_memory-2.3.0 → cortex_ai_memory-2.4.0}/cortex-core/tests/test_compression.rs +0 -0
- {cortex_ai_memory-2.3.0 → cortex_ai_memory-2.4.0}/cortex-core/tests/test_compression_advanced.rs +0 -0
- {cortex_ai_memory-2.3.0 → cortex_ai_memory-2.4.0}/cortex-core/tests/test_consolidation.rs +0 -0
- {cortex_ai_memory-2.3.0 → cortex_ai_memory-2.4.0}/cortex-core/tests/test_consolidation_advanced.rs +0 -0
- {cortex_ai_memory-2.3.0 → cortex_ai_memory-2.4.0}/cortex-core/tests/test_context.rs +0 -0
- {cortex_ai_memory-2.3.0 → cortex_ai_memory-2.4.0}/cortex-core/tests/test_contradiction_flow.rs +0 -0
- {cortex_ai_memory-2.3.0 → cortex_ai_memory-2.4.0}/cortex-core/tests/test_cortex_e2e.rs +0 -0
- {cortex_ai_memory-2.3.0 → cortex_ai_memory-2.4.0}/cortex-core/tests/test_cortex_full.rs +0 -0
- {cortex_ai_memory-2.3.0 → cortex_ai_memory-2.4.0}/cortex-core/tests/test_episode.rs +0 -0
- {cortex_ai_memory-2.3.0 → cortex_ai_memory-2.4.0}/cortex-core/tests/test_events.rs +0 -0
- {cortex_ai_memory-2.3.0 → cortex_ai_memory-2.4.0}/cortex-core/tests/test_full_privacy_chain.rs +0 -0
- {cortex_ai_memory-2.3.0 → cortex_ai_memory-2.4.0}/cortex-core/tests/test_gdrive_real.rs +0 -0
- {cortex_ai_memory-2.3.0 → cortex_ai_memory-2.4.0}/cortex-core/tests/test_ingest_parity.rs +0 -0
- {cortex_ai_memory-2.3.0 → cortex_ai_memory-2.4.0}/cortex-core/tests/test_memory_index.rs +0 -0
- {cortex_ai_memory-2.3.0 → cortex_ai_memory-2.4.0}/cortex-core/tests/test_optimizations.rs +0 -0
- {cortex_ai_memory-2.3.0 → cortex_ai_memory-2.4.0}/cortex-core/tests/test_people.rs +0 -0
- {cortex_ai_memory-2.3.0 → cortex_ai_memory-2.4.0}/cortex-core/tests/test_plugin.rs +0 -0
- {cortex_ai_memory-2.3.0 → cortex_ai_memory-2.4.0}/cortex-core/tests/test_plugin_manager_ext.rs +0 -0
- {cortex_ai_memory-2.3.0 → cortex_ai_memory-2.4.0}/cortex-core/tests/test_privacy_sync.rs +0 -0
- {cortex_ai_memory-2.3.0 → cortex_ai_memory-2.4.0}/cortex-core/tests/test_procedural.rs +0 -0
- {cortex_ai_memory-2.3.0 → cortex_ai_memory-2.4.0}/cortex-core/tests/test_retrieval.rs +0 -0
- {cortex_ai_memory-2.3.0 → cortex_ai_memory-2.4.0}/cortex-core/tests/test_retrieval_advanced.rs +0 -0
- {cortex_ai_memory-2.3.0 → cortex_ai_memory-2.4.0}/cortex-core/tests/test_semantic.rs +0 -0
- {cortex_ai_memory-2.3.0 → cortex_ai_memory-2.4.0}/cortex-core/tests/test_storage.rs +0 -0
- {cortex_ai_memory-2.3.0 → cortex_ai_memory-2.4.0}/cortex-core/tests/test_storage_advanced.rs +0 -0
- {cortex_ai_memory-2.3.0 → cortex_ai_memory-2.4.0}/cortex-core/tests/test_temporal_retrieval.rs +0 -0
- {cortex_ai_memory-2.3.0 → cortex_ai_memory-2.4.0}/cortex-core/tests/test_traits_defaults.rs +0 -0
- {cortex_ai_memory-2.3.0 → cortex_ai_memory-2.4.0}/cortex-core/tests/test_types.rs +0 -0
- {cortex_ai_memory-2.3.0 → cortex_ai_memory-2.4.0}/cortex-core/tests/test_working_memory.rs +0 -0
- {cortex_ai_memory-2.3.0 → cortex_ai_memory-2.4.0}/cortex-python/README.md +0 -0
- {cortex_ai_memory-2.3.0 → cortex_ai_memory-2.4.0}/cortex-python/src/lib.rs +0 -0
|
@@ -573,7 +573,7 @@ dependencies = [
|
|
|
573
573
|
|
|
574
574
|
[[package]]
|
|
575
575
|
name = "cortex-core"
|
|
576
|
-
version = "2.
|
|
576
|
+
version = "2.4.0"
|
|
577
577
|
dependencies = [
|
|
578
578
|
"aes-gcm",
|
|
579
579
|
"argon2",
|
|
@@ -606,7 +606,7 @@ dependencies = [
|
|
|
606
606
|
|
|
607
607
|
[[package]]
|
|
608
608
|
name = "cortex-http"
|
|
609
|
-
version = "2.
|
|
609
|
+
version = "2.4.0"
|
|
610
610
|
dependencies = [
|
|
611
611
|
"axum",
|
|
612
612
|
"chrono",
|
|
@@ -623,19 +623,19 @@ dependencies = [
|
|
|
623
623
|
|
|
624
624
|
[[package]]
|
|
625
625
|
name = "cortex-mcp-server"
|
|
626
|
-
version = "2.
|
|
626
|
+
version = "2.4.0"
|
|
627
627
|
dependencies = [
|
|
628
628
|
"axum",
|
|
629
629
|
"chrono",
|
|
630
630
|
"clap",
|
|
631
631
|
"cortex-core",
|
|
632
632
|
"hostname",
|
|
633
|
+
"libc",
|
|
633
634
|
"rand 0.8.5",
|
|
634
635
|
"serde",
|
|
635
636
|
"serde_json",
|
|
636
637
|
"tokio",
|
|
637
638
|
"tower",
|
|
638
|
-
"tower-http",
|
|
639
639
|
"tracing",
|
|
640
640
|
"tracing-subscriber",
|
|
641
641
|
"uuid",
|
|
@@ -643,7 +643,7 @@ dependencies = [
|
|
|
643
643
|
|
|
644
644
|
[[package]]
|
|
645
645
|
name = "cortex-python"
|
|
646
|
-
version = "2.
|
|
646
|
+
version = "2.4.0"
|
|
647
647
|
dependencies = [
|
|
648
648
|
"cortex-core",
|
|
649
649
|
"pyo3",
|
|
@@ -652,7 +652,7 @@ dependencies = [
|
|
|
652
652
|
|
|
653
653
|
[[package]]
|
|
654
654
|
name = "cortex-wasm"
|
|
655
|
-
version = "2.
|
|
655
|
+
version = "2.4.0"
|
|
656
656
|
dependencies = [
|
|
657
657
|
"chrono",
|
|
658
658
|
"js-sys",
|
|
@@ -3507,7 +3507,6 @@ dependencies = [
|
|
|
3507
3507
|
"http-body",
|
|
3508
3508
|
"iri-string",
|
|
3509
3509
|
"pin-project-lite",
|
|
3510
|
-
"tokio",
|
|
3511
3510
|
"tower",
|
|
3512
3511
|
"tower-layer",
|
|
3513
3512
|
"tower-service",
|
|
@@ -117,6 +117,13 @@ pub fn import_all(
|
|
|
117
117
|
};
|
|
118
118
|
|
|
119
119
|
if let Some(memories) = data.memories {
|
|
120
|
+
// The Muse export is consent given on this device. Import never recreates it:
|
|
121
|
+
// a crafted file could otherwise plant rows Muse would serve. After a restore, the
|
|
122
|
+
// user re-exports with `gateway allow`.
|
|
123
|
+
let memories: Vec<_> = memories
|
|
124
|
+
.into_iter()
|
|
125
|
+
.filter(|m| m.namespace.as_deref() != Some(crate::types::MUSE_EXPORT_NAMESPACE))
|
|
126
|
+
.collect();
|
|
120
127
|
report.memories = memories.len();
|
|
121
128
|
for mem in &memories {
|
|
122
129
|
storage.store_memory(mem)?;
|
|
@@ -559,15 +559,14 @@ impl Cortex {
|
|
|
559
559
|
Ok(())
|
|
560
560
|
}
|
|
561
561
|
|
|
562
|
-
/// Auto-generate embedding if embedder is available and none provided.
|
|
563
|
-
/// Lazily initializes the embedding model on first call.
|
|
564
|
-
#[allow(unused_variables)]
|
|
565
562
|
/// Embed `text` with the local model, or `None` when embeddings are compiled out,
|
|
566
563
|
/// disabled via `CORTEX_NO_EMBEDDINGS`, or the model failed to load.
|
|
567
564
|
pub fn embed_query(&self, text: &str) -> Option<Vec<f32>> {
|
|
568
565
|
self.auto_embed(text, None)
|
|
569
566
|
}
|
|
570
567
|
|
|
568
|
+
/// Auto-generate embedding if embedder is available and none provided.
|
|
569
|
+
/// Lazily initializes the embedding model on first call.
|
|
571
570
|
fn auto_embed(&self, text: &str, embedding: Option<Vec<f32>>) -> Option<Vec<f32>> {
|
|
572
571
|
#[cfg(not(feature = "embeddings"))]
|
|
573
572
|
let _ = text;
|
|
@@ -818,8 +817,19 @@ impl Cortex {
|
|
|
818
817
|
return None;
|
|
819
818
|
}
|
|
820
819
|
let emb = embedding.as_ref()?;
|
|
821
|
-
|
|
822
|
-
|
|
820
|
+
// A Muse export copy is not the user's memory: merging into it would drop the new
|
|
821
|
+
// ingest (and a later `gateway revoke` would lose the only copy). Skip export copies
|
|
822
|
+
// and keep looking at the next-nearest neighbours instead of giving up.
|
|
823
|
+
const NEAR_DEDUP_CANDIDATES: usize = 8;
|
|
824
|
+
let is_export_copy = |id: Uuid| {
|
|
825
|
+
matches!(self.storage.get_memory(id),
|
|
826
|
+
Ok(Some(m)) if m.namespace.as_deref() == Some(crate::types::MUSE_EXPORT_NAMESPACE))
|
|
827
|
+
};
|
|
828
|
+
for (existing_id, sim) in self.index.search(emb, NEAR_DEDUP_CANDIDATES) {
|
|
829
|
+
if sim < self.dedup_config.near_dedup_threshold {
|
|
830
|
+
break; // results are ordered by similarity
|
|
831
|
+
}
|
|
832
|
+
if !is_export_copy(existing_id) {
|
|
823
833
|
return Some(NearDup::Existing(existing_id));
|
|
824
834
|
}
|
|
825
835
|
}
|
|
@@ -1456,6 +1466,13 @@ impl Cortex {
|
|
|
1456
1466
|
.storage
|
|
1457
1467
|
.get_memory(id)?
|
|
1458
1468
|
.ok_or_else(|| CortexError::NotFound(format!("memory {id}")))?;
|
|
1469
|
+
// Muse export copies are local-only consent records; they must never reach sync.
|
|
1470
|
+
if privacy.is_syncable() && mem.namespace.as_deref() == Some(crate::types::MUSE_EXPORT_NAMESPACE) {
|
|
1471
|
+
return Err(CortexError::InvalidInput(format!(
|
|
1472
|
+
"memories in the reserved '{}' namespace cannot be made syncable",
|
|
1473
|
+
crate::types::MUSE_EXPORT_NAMESPACE
|
|
1474
|
+
)));
|
|
1475
|
+
}
|
|
1459
1476
|
let was_syncable = mem.privacy.is_syncable();
|
|
1460
1477
|
mem.privacy = privacy;
|
|
1461
1478
|
self.storage.update_memory(&mem)?;
|
|
@@ -113,6 +113,25 @@ pub struct EncryptionManifest {
|
|
|
113
113
|
pub hmac_salt: Option<String>, // base64-encoded salt for HMAC key derivation
|
|
114
114
|
#[serde(skip_serializing_if = "Option::is_none")]
|
|
115
115
|
pub hmac: Option<String>, // base64-encoded HMAC of manifest content (computed without hmac and hmac_salt fields)
|
|
116
|
+
/// The snapshot new devices must bootstrap from. Covered by the manifest HMAC, so an
|
|
117
|
+
/// attacker without the passphrase can neither replay an older snapshot (same name, older
|
|
118
|
+
/// content) nor make restore fall back to one. Omitted when `None`, which keeps the HMAC
|
|
119
|
+
/// input of manifests written before this field existed byte-identical.
|
|
120
|
+
#[serde(default, skip_serializing_if = "Option::is_none")]
|
|
121
|
+
pub latest_snapshot: Option<SnapshotPointer>,
|
|
122
|
+
/// Monotonic counter bumped on every manifest write (first creation = 1). Covered by the
|
|
123
|
+
/// HMAC; devices remember the highest value they accepted and reject lower ones, so a
|
|
124
|
+
/// replayed older manifest (with its older snapshot pointer or key version) is detected.
|
|
125
|
+
/// Absent in legacy manifests (treated as 0); omitted when `None` for HMAC compatibility.
|
|
126
|
+
#[serde(default, skip_serializing_if = "Option::is_none")]
|
|
127
|
+
pub generation: Option<u64>,
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/// Names the authoritative encrypted snapshot: its file name and its `HMAC:` line value.
|
|
131
|
+
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
|
132
|
+
pub struct SnapshotPointer {
|
|
133
|
+
pub file: String,
|
|
134
|
+
pub mac: String,
|
|
116
135
|
}
|
|
117
136
|
|
|
118
137
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
@@ -140,6 +159,8 @@ pub fn new_encryption_manifest() -> EncryptionManifest {
|
|
|
140
159
|
key_version: Some(0), // Start at version 0; higher versions for rotated keys
|
|
141
160
|
hmac_salt: None, // Will be computed during sync initialization
|
|
142
161
|
hmac: None, // HMAC will be computed during sync initialization
|
|
162
|
+
latest_snapshot: None,
|
|
163
|
+
generation: None,
|
|
143
164
|
}
|
|
144
165
|
}
|
|
145
166
|
|
|
@@ -285,6 +306,19 @@ pub fn decrypt_line(ctx: &CryptoContext, encrypted_line: &str) -> Result<Vec<u8>
|
|
|
285
306
|
}
|
|
286
307
|
}
|
|
287
308
|
|
|
309
|
+
/// Key version of an envelope: `ENC1` is version 0; `ENC2` carries it in its first two
|
|
310
|
+
/// payload bytes. `None` if the line is not a well-formed envelope header. Only the first
|
|
311
|
+
/// base64 quantum is decoded, so this is cheap even for a large snapshot envelope.
|
|
312
|
+
pub fn envelope_version(line: &str) -> Option<u32> {
|
|
313
|
+
if line.starts_with(ENC_PREFIX) {
|
|
314
|
+
return Some(0);
|
|
315
|
+
}
|
|
316
|
+
let head = line.strip_prefix(ENC2_PREFIX)?.get(..4)?;
|
|
317
|
+
let bytes = base64::engine::general_purpose::STANDARD.decode(head).ok()?;
|
|
318
|
+
let version = bytes.get(..VERSION_LEN)?;
|
|
319
|
+
Some(u16::from_le_bytes([version[0], version[1]]) as u32)
|
|
320
|
+
}
|
|
321
|
+
|
|
288
322
|
/// Check if a line is encrypted (either envelope version).
|
|
289
323
|
pub fn is_encrypted_line(line: &str) -> bool {
|
|
290
324
|
line.starts_with(ENC_PREFIX) || line.starts_with(ENC2_PREFIX)
|
|
@@ -588,4 +622,48 @@ mod tests {
|
|
|
588
622
|
assert!(is_encrypted_line("ENC1:abc123"));
|
|
589
623
|
assert!(!is_encrypted_line("{\"op_id\":\"...\"}"));
|
|
590
624
|
}
|
|
625
|
+
#[test]
|
|
626
|
+
fn test_envelope_version() {
|
|
627
|
+
let m = test_manifest();
|
|
628
|
+
let ctx0 = derive_key("pass", &m).unwrap();
|
|
629
|
+
let ctx3 = derive_key("pass", &manifest_at_version(&m, 3)).unwrap();
|
|
630
|
+
assert_eq!(envelope_version(&encrypt_line(&ctx0, b"x").unwrap()), Some(0));
|
|
631
|
+
assert_eq!(envelope_version(&encrypt_line(&ctx3, b"x").unwrap()), Some(3));
|
|
632
|
+
assert_eq!(envelope_version("ENC2:"), None);
|
|
633
|
+
assert_eq!(envelope_version("ENC2:!!!!"), None);
|
|
634
|
+
assert_eq!(envelope_version("{\"op\":1}"), None);
|
|
635
|
+
}
|
|
636
|
+
|
|
637
|
+
/// Adding `latest_snapshot` must not change the HMAC input of manifests that lack it:
|
|
638
|
+
/// a manifest written before the field existed still verifies after a parse/re-serialize.
|
|
639
|
+
#[test]
|
|
640
|
+
fn test_legacy_manifest_without_pointer_still_verifies() {
|
|
641
|
+
let mut legacy = new_encryption_manifest();
|
|
642
|
+
legacy.hmac = None;
|
|
643
|
+
legacy.hmac_salt = None;
|
|
644
|
+
// Legacy wire form: exactly the pre-pointer field set.
|
|
645
|
+
let legacy_bytes = format!(
|
|
646
|
+
r#"{{"algorithm":"{}","kdf":"{}","salt":"{}","kdf_params":{{"time_cost":{},"mem_cost":{},"parallelism":{}}},"key_version":0}}"#,
|
|
647
|
+
legacy.algorithm,
|
|
648
|
+
legacy.kdf,
|
|
649
|
+
legacy.salt,
|
|
650
|
+
legacy.kdf_params.time_cost,
|
|
651
|
+
legacy.kdf_params.mem_cost,
|
|
652
|
+
legacy.kdf_params.parallelism,
|
|
653
|
+
)
|
|
654
|
+
.into_bytes();
|
|
655
|
+
let (hmac, salt) = compute_manifest_hmac(&legacy_bytes, "pass").unwrap();
|
|
656
|
+
|
|
657
|
+
let parsed: EncryptionManifest = serde_json::from_slice(&legacy_bytes).unwrap();
|
|
658
|
+
assert!(parsed.latest_snapshot.is_none());
|
|
659
|
+
let reserialized = serde_json::to_vec(&parsed).unwrap();
|
|
660
|
+
assert_eq!(reserialized, legacy_bytes);
|
|
661
|
+
assert!(verify_manifest_integrity(&reserialized, &hmac, "pass", Some(&salt)).unwrap());
|
|
662
|
+
|
|
663
|
+
// With a pointer the bytes (and so the HMAC) change — the pointer is authenticated.
|
|
664
|
+
let mut pinned = parsed.clone();
|
|
665
|
+
pinned.latest_snapshot = Some(SnapshotPointer { file: "f".into(), mac: "m".into() });
|
|
666
|
+
let pinned_bytes = serde_json::to_vec(&pinned).unwrap();
|
|
667
|
+
assert!(!verify_manifest_integrity(&pinned_bytes, &hmac, "pass", Some(&salt)).unwrap());
|
|
668
|
+
}
|
|
591
669
|
}
|