fix(community): 原子确认多密钥来源信任

This commit is contained in:
2026-09-08 22:12:49 +08:00
parent 86b9d06dcc
commit 7bdc66ef75
9 changed files with 188 additions and 5 deletions
+85
View File
@@ -374,6 +374,39 @@ impl ExtensionStore {
params![setting.source,setting.namespace,setting.key_id,serde_json::to_string(setting).unwrap(),revision])?;
Ok(revision)
}
pub fn confirm_trust_group(
&mut self,
proposals: &[(TrustSetting, Option<String>, String)],
) -> Result<Vec<String>> {
if proposals.is_empty() || proposals.len() > 64 {
return Err(HostError::new("EXTENSION_TRUST_INVALID"));
}
let mut identities = std::collections::BTreeSet::new();
for (setting, _, _) in proposals {
if setting.source != proposals[0].0.source
|| setting.source_id != proposals[0].0.source_id
|| !identities.insert((&setting.namespace, &setting.key_id))
{
return Err(HostError::new("EXTENSION_TRUST_INVALID"));
}
}
self.db
.execute_batch("SAVEPOINT trust_confirmation_group")?;
let result = (|| {
let mut revisions = Vec::new();
for (setting, expected, confirmed) in proposals {
revisions.push(self.confirm_trust(setting, expected.as_deref(), confirmed)?);
}
self.db.execute_batch("RELEASE trust_confirmation_group")?;
Ok(revisions)
})();
if result.is_err() {
self.db.execute_batch(
"ROLLBACK TO trust_confirmation_group; RELEASE trust_confirmation_group",
)?;
}
result
}
fn block_identity(
source_url: &str,
release: &Release,
@@ -1112,6 +1145,58 @@ mod tests {
}
}
#[test]
fn grouped_trust_confirmation_rolls_back_earlier_keys_on_later_conflict() {
let temp = tempfile::tempdir().unwrap();
let (_, _, key) = fixture();
let mut store = ExtensionStore::open(temp.path()).unwrap();
let first = TrustSetting {
source: "https://catalog.example/".into(),
source_id: "catalog".into(),
namespace: "examples".into(),
key_id: "first".into(),
public_key: key,
enabled: true,
};
let mut second = first.clone();
second.key_id = "second".into();
store
.confirm_trust(&second, None, &second.fingerprint().unwrap())
.unwrap();
second.enabled = false;
let proposals = vec![
(first.clone(), None, first.fingerprint().unwrap()),
(second.clone(), None, second.fingerprint().unwrap()),
];
assert!(store.confirm_trust_group(&proposals).is_err());
drop(store);
let mut store = ExtensionStore::open(temp.path()).unwrap();
assert!(store
.trust_setting(&first.source, &first.namespace, &first.key_id)
.unwrap()
.is_none());
let old = store
.trust_setting(&second.source, &second.namespace, &second.key_id)
.unwrap()
.unwrap();
assert!(old.enabled);
let mut proposals = proposals;
proposals[1].1 = Some(old.fingerprint().unwrap());
store.confirm_trust_group(&proposals).unwrap();
assert!(store
.trust_setting(&first.source, &first.namespace, &first.key_id)
.unwrap()
.is_some());
assert!(
!store
.trust_setting(&second.source, &second.namespace, &second.key_id)
.unwrap()
.unwrap()
.enabled
);
let duplicate = vec![proposals[0].clone(), proposals[0].clone()];
assert!(store.confirm_trust_group(&duplicate).is_err());
}
#[test]
fn cancelled_stage_reports_durable_commit_race_truthfully() {
let (release, archive, key) = fixture();
for boundary in ["object_stored", "receipt_recorded", "committed"] {