[Fix] Clean
Some checks failed
CI / checks (push) Failing after 16m34s

This commit is contained in:
Alex Emmet 2026-08-18 21:53:02 +02:00
commit b331b9f6a3
No known key found for this signature in database
12 changed files with 433 additions and 75 deletions

View file

@ -916,9 +916,19 @@ mod tests {
let mut signer_public_keys = recipient.public_key_bundle(); let mut signer_public_keys = recipient.public_key_bundle();
signer_public_keys.sig_cl_public_key = signer_public_key; signer_public_keys.sig_cl_public_key = signer_public_key;
signed.verify(SENDER_ID, &signer_public_keys, ProtectionPurpose::from(1))?; signed.verify_with_policy(
SENDER_ID,
&signer_public_keys,
ProtectionPurpose::from(1),
crate::ProtectionPolicy::any_supported(),
)?;
assert_eq!( assert_eq!(
signed.into_verified(SENDER_ID, &signer_public_keys, ProtectionPurpose::from(1))?, signed.into_verified_with_policy(
SENDER_ID,
&signer_public_keys,
ProtectionPurpose::from(1),
crate::ProtectionPolicy::any_supported(),
)?,
clear_payload clear_payload
); );
Ok(()) Ok(())

View file

@ -2391,10 +2391,11 @@ mod tests {
assert_eq!(&encoded[15 + signature_len..], inner); assert_eq!(&encoded[15 + signature_len..], inner);
let decoded = DataValue::from_bytes(&encoded).ok_or("signed value did not decode")?; let decoded = DataValue::from_bytes(&encoded).ok_or("signed value did not decode")?;
decoded.verify( decoded.verify_with_policy(
0x0102_0304_0506_0708, 0x0102_0304_0506_0708,
&public_keys, &public_keys,
ProtectionPurpose::from(0xA5), ProtectionPurpose::from(0xA5),
ProtectionPolicy::any_supported(),
)?; )?;
assert!(matches!( assert!(matches!(
decoded.clone().into_verified_with_policy( decoded.clone().into_verified_with_policy(
@ -2406,16 +2407,18 @@ mod tests {
Err(ProtectionError::SignaturePolicyMismatch { .. }) Err(ProtectionError::SignaturePolicyMismatch { .. })
)); ));
// Verification is non-consuming, so it can safely be repeated. // Verification is non-consuming, so it can safely be repeated.
decoded.verify( decoded.verify_with_policy(
0x0102_0304_0506_0708, 0x0102_0304_0506_0708,
&public_keys, &public_keys,
ProtectionPurpose::from(0xA5), ProtectionPurpose::from(0xA5),
ProtectionPolicy::any_supported(),
)?; )?;
assert_eq!( assert_eq!(
decoded.clone().into_verified( decoded.clone().into_verified_with_policy(
0x0102_0304_0506_0708, 0x0102_0304_0506_0708,
&public_keys, &public_keys,
ProtectionPurpose::from(0xA5), ProtectionPurpose::from(0xA5),
ProtectionPolicy::any_supported(),
)?, )?,
original original
); );
@ -2424,10 +2427,11 @@ mod tests {
return Err("expected signed value".into()); return Err("expected signed value".into());
}; };
assert_eq!( assert_eq!(
wrapper.into_verified( wrapper.into_verified_with_policy(
0x0102_0304_0506_0708, 0x0102_0304_0506_0708,
&public_keys, &public_keys,
ProtectionPurpose::from(0xA5), ProtectionPurpose::from(0xA5),
ProtectionPolicy::any_supported(),
)?, )?,
original original
); );
@ -2503,7 +2507,12 @@ mod tests {
}; };
wrong_purpose.purpose ^= 1; wrong_purpose.purpose ^= 1;
assert!(matches!( assert!(matches!(
wrong_purpose.verify(41, &public_keys, ProtectionPurpose::from(7)), wrong_purpose.verify_with_policy(
41,
&public_keys,
ProtectionPurpose::from(7),
ProtectionPolicy::any_supported(),
),
Err(ProtectionError::PurposeMismatch { .. }) Err(ProtectionError::PurposeMismatch { .. })
)); ));
@ -2512,7 +2521,12 @@ mod tests {
}; };
wrong_signer_id.signer_id ^= 1; wrong_signer_id.signer_id ^= 1;
assert!(matches!( assert!(matches!(
wrong_signer_id.verify(41, &public_keys, ProtectionPurpose::from(7)), wrong_signer_id.verify_with_policy(
41,
&public_keys,
ProtectionPurpose::from(7),
ProtectionPolicy::any_supported(),
),
Err(ProtectionError::SignerIdMismatch { .. }) Err(ProtectionError::SignerIdMismatch { .. })
)); ));
@ -2521,7 +2535,12 @@ mod tests {
}; };
wrong_signature.signature[0] ^= 1; wrong_signature.signature[0] ^= 1;
assert!(matches!( assert!(matches!(
wrong_signature.verify(41, &public_keys, ProtectionPurpose::from(7)), wrong_signature.verify_with_policy(
41,
&public_keys,
ProtectionPurpose::from(7),
ProtectionPolicy::any_supported(),
),
Err(ProtectionError::InvalidSignature) Err(ProtectionError::InvalidSignature)
)); ));
@ -2530,7 +2549,12 @@ mod tests {
}; };
*wrong_value.value = DataValue::Str("replacement".into()); *wrong_value.value = DataValue::Str("replacement".into());
assert!(matches!( assert!(matches!(
wrong_value.verify(41, &public_keys, ProtectionPurpose::from(7)), wrong_value.verify_with_policy(
41,
&public_keys,
ProtectionPurpose::from(7),
ProtectionPolicy::any_supported(),
),
Err(ProtectionError::InvalidSignature) Err(ProtectionError::InvalidSignature)
)); ));
Ok(()) Ok(())
@ -2593,9 +2617,19 @@ mod tests {
let opened = decoded.decrypt(&keyring, ProtectionPurpose::from(2))?; let opened = decoded.decrypt(&keyring, ProtectionPurpose::from(2))?;
let mut public_keys = keyring.public_key_bundle(); let mut public_keys = keyring.public_key_bundle();
public_keys.sig_cl_public_key = signer_public; public_keys.sig_cl_public_key = signer_public;
opened.verify(7, &public_keys, ProtectionPurpose::from(1))?; opened.verify_with_policy(
7,
&public_keys,
ProtectionPurpose::from(1),
ProtectionPolicy::any_supported(),
)?;
assert_eq!( assert_eq!(
opened.into_verified(7, &public_keys, ProtectionPurpose::from(1))?, opened.into_verified_with_policy(
7,
&public_keys,
ProtectionPurpose::from(1),
ProtectionPolicy::any_supported(),
)?,
value value
); );
Ok(()) Ok(())
@ -2629,8 +2663,18 @@ mod tests {
return Err("expected signed outer wrapper".into()); return Err("expected signed outer wrapper".into());
}; };
assert_eq!(signed.signer_id, 7); assert_eq!(signed.signer_id, 7);
signed.verify(7, &public_keys, ProtectionPurpose::from(1))?; signed.verify_with_policy(
let encrypted = signed.into_verified(7, &public_keys, ProtectionPurpose::from(1))?; 7,
&public_keys,
ProtectionPurpose::from(1),
ProtectionPolicy::any_supported(),
)?;
let encrypted = signed.into_verified_with_policy(
7,
&public_keys,
ProtectionPurpose::from(1),
ProtectionPolicy::any_supported(),
)?;
assert!(matches!(encrypted, DataValue::Encrypted(_))); assert!(matches!(encrypted, DataValue::Encrypted(_)));
assert_eq!( assert_eq!(
encrypted.decrypt(&keyring, ProtectionPurpose::from(2))?, encrypted.decrypt(&keyring, ProtectionPurpose::from(2))?,
@ -2680,10 +2724,11 @@ mod tests {
let mut signer_keys = outer_recipient.public_key_bundle(); let mut signer_keys = outer_recipient.public_key_bundle();
signer_keys.sig_cl_public_key = signer_public; signer_keys.sig_cl_public_key = signer_public;
let middle = outer_signed.into_verified( let middle = outer_signed.into_verified_with_policy(
OUTER_SIGNER_ID, OUTER_SIGNER_ID,
&signer_keys, &signer_keys,
ProtectionPurpose::from(1), ProtectionPurpose::from(1),
ProtectionPolicy::any_supported(),
)?; )?;
let DataValue::Container(entries) = middle else { let DataValue::Container(entries) = middle else {
return Err("expected container inside outer signature".into()); return Err("expected container inside outer signature".into());
@ -2700,10 +2745,11 @@ mod tests {
}; };
assert_eq!(inner_wrapper.signer_id, INNER_SIGNER_ID); assert_eq!(inner_wrapper.signer_id, INNER_SIGNER_ID);
assert_eq!( assert_eq!(
inner_signed.into_verified( inner_signed.into_verified_with_policy(
INNER_SIGNER_ID, INNER_SIGNER_ID,
&signer_keys, &signer_keys,
ProtectionPurpose::from(3), ProtectionPurpose::from(3),
ProtectionPolicy::any_supported(),
)?, )?,
leaf leaf
); );
@ -2861,11 +2907,21 @@ mod tests {
let public_keys = keyring.public_key_bundle(); let public_keys = keyring.public_key_bundle();
assert!(matches!( assert!(matches!(
DataValue::Null.verify(1, &public_keys, ProtectionPurpose::from(1)), DataValue::Null.verify_with_policy(
1,
&public_keys,
ProtectionPurpose::from(1),
ProtectionPolicy::any_supported(),
),
Err(ProtectionError::NotSigned) Err(ProtectionError::NotSigned)
)); ));
assert!(matches!( assert!(matches!(
DataValue::Null.into_verified(1, &public_keys, ProtectionPurpose::from(1)), DataValue::Null.into_verified_with_policy(
1,
&public_keys,
ProtectionPurpose::from(1),
ProtectionPolicy::any_supported(),
),
Err(ProtectionError::NotSigned) Err(ProtectionError::NotSigned)
)); ));
assert!(matches!( assert!(matches!(
@ -2909,7 +2965,12 @@ mod tests {
}; };
signed.signature[0] ^= 1; signed.signature[0] ^= 1;
assert!(matches!( assert!(matches!(
DataValue::Signed(signed).verify(1, &signing_keys, ProtectionPurpose::from(1)), DataValue::Signed(signed).verify_with_policy(
1,
&signing_keys,
ProtectionPurpose::from(1),
ProtectionPolicy::any_supported(),
),
Err(ProtectionError::InvalidSignature) Err(ProtectionError::InvalidSignature)
)); ));

View file

@ -133,6 +133,10 @@ impl InMemoryReplayGuard {
pub fn len(&self) -> usize { pub fn len(&self) -> usize {
self.accepted.len() self.accepted.len()
} }
pub fn is_empty(&self) -> bool {
self.accepted.is_empty()
}
} }
impl ReplayGuard for InMemoryReplayGuard { impl ReplayGuard for InMemoryReplayGuard {

View file

@ -1022,6 +1022,18 @@ mod tests {
Ed25519Signer::new(&keyring.sig_cl_secret_key).expect("Ed25519 signer") Ed25519Signer::new(&keyring.sig_cl_secret_key).expect("Ed25519 signer")
} }
fn content_open_options(
metadata: &VerifiedRelayMetadata,
policy: ProtectionPolicy,
) -> RelayOpenOptions {
RelayOpenOptions {
policy,
decode_limits: metadata.decode_limits,
encode_limits: metadata.encode_limits,
protected_limits: metadata.protected_limits,
}
}
// Test-only compatibility shims keep older fixture setup readable while // Test-only compatibility shims keep older fixture setup readable while
// routing every invocation to an explicit replay choice in production. // routing every invocation to an explicit replay choice in production.
fn open_relay_metadata( fn open_relay_metadata(
@ -1345,12 +1357,12 @@ mod tests {
) )
.expect("final recipient metadata"); .expect("final recipient metadata");
assert_eq!(final_metadata.metadata(), Some(&application_metadata)); assert_eq!(final_metadata.metadata(), Some(&application_metadata));
let final_content = open_relay_content( let final_content = open_relay_content_with_limits_without_replay(
&final_metadata, &final_metadata,
&final_recipient, &[&final_recipient],
&sender.public_key_bundle(), std::slice::from_ref(&sender.public_key_bundle()),
42, Some(42),
policy, content_open_options(&final_metadata, policy),
) )
.expect("final recipient content"); .expect("final recipient content");
assert_eq!(final_content.content, DataValue::Str("hello".into())); assert_eq!(final_content.content, DataValue::Str("hello".into()));
@ -1393,22 +1405,22 @@ mod tests {
)); ));
assert!( assert!(
open_relay_content( open_relay_content_with_limits_without_replay(
&metadata, &metadata,
&metadata_recipient, &[&metadata_recipient],
&sender.public_key_bundle(), std::slice::from_ref(&sender.public_key_bundle()),
42, Some(42),
policy, content_open_options(&metadata, policy),
) )
.is_err() .is_err()
); );
let content = open_relay_content( let content = open_relay_content_with_limits_without_replay(
&metadata, &metadata,
&final_recipient, &[&final_recipient],
&sender.public_key_bundle(), std::slice::from_ref(&sender.public_key_bundle()),
42, Some(42),
policy, content_open_options(&metadata, policy),
) )
.expect("content"); .expect("content");
assert_eq!(content.signer_id, 7); assert_eq!(content.signer_id, 7);
@ -1416,12 +1428,12 @@ mod tests {
assert_eq!(content.message_type, "ProtectedMessage"); assert_eq!(content.message_type, "ProtectedMessage");
assert_eq!(content.content, DataValue::Str("hello".into())); assert_eq!(content.content, DataValue::Str("hello".into()));
assert!(matches!( assert!(matches!(
open_relay_content( open_relay_content_with_limits_without_replay(
&metadata, &metadata,
&final_recipient, &[&final_recipient],
&sender.public_key_bundle(), std::slice::from_ref(&sender.public_key_bundle()),
43, Some(43),
policy, content_open_options(&metadata, policy),
), ),
Err(RelayError::NotFinalRecipient) Err(RelayError::NotFinalRecipient)
)); ));
@ -1512,7 +1524,12 @@ mod tests {
.expect("relay frame"); .expect("relay frame");
assert_eq!( assert_eq!(
relay_metadata_claimed_signer_id(&frame, &[&recipient]).expect("signer ID"), relay_metadata_claimed_signer_id_with_limits(
&frame,
&[&recipient],
DecodeLimits::default(),
)
.expect("signer ID"),
7 7
); );
let metadata = open_relay_metadata_with_keys( let metadata = open_relay_metadata_with_keys(
@ -1523,12 +1540,15 @@ mod tests {
ProtectionPolicy::from(crate::SignaturePolicy::Ed25519), ProtectionPolicy::from(crate::SignaturePolicy::Ed25519),
) )
.expect("relay metadata"); .expect("relay metadata");
let content = open_relay_content_with_keyrings( let content = open_relay_content_with_limits_without_replay(
&metadata, &metadata,
&[&recipient], &[&recipient],
&[sender.public_key_bundle()], &[sender.public_key_bundle()],
Some(42), Some(42),
ProtectionPolicy::from(crate::SignaturePolicy::Ed25519), content_open_options(
&metadata,
ProtectionPolicy::from(crate::SignaturePolicy::Ed25519),
),
) )
.expect("relay content"); .expect("relay content");
@ -1571,12 +1591,12 @@ mod tests {
) )
.expect("relay metadata"); .expect("relay metadata");
let content = open_relay_content_with_keyrings( let content = open_relay_content_with_limits_without_replay(
&metadata, &metadata,
&[&current_content_recipient, &previous_content_recipient], &[&current_content_recipient, &previous_content_recipient],
&[sender.public_key_bundle()], &[sender.public_key_bundle()],
Some(42), Some(42),
policy, content_open_options(&metadata, policy),
) )
.expect("previous content recipient key should decrypt"); .expect("previous content recipient key should decrypt");
@ -1680,12 +1700,12 @@ mod tests {
) )
.expect("metadata signed by a previous key should verify"); .expect("metadata signed by a previous key should verify");
assert_eq!(metadata.matched_signer_key_index(), 1); assert_eq!(metadata.matched_signer_key_index(), 1);
let content = open_relay_content_with_keys( let content = open_relay_content_with_limits_without_replay(
&metadata, &metadata,
&recipient, &[&recipient],
&[current_signer_public, old_signer_public], &[current_signer_public, old_signer_public],
42, Some(42),
policy, content_open_options(&metadata, policy),
) )
.expect("content signed by a previous key should verify"); .expect("content signed by a previous key should verify");
assert_eq!(content.message_type, "ProtectedMessage"); assert_eq!(content.message_type, "ProtectedMessage");

243
create-web-release.mjs Normal file
View file

@ -0,0 +1,243 @@
#!/usr/bin/env node
import { execFile, spawn } from "node:child_process";
import { access, cp, mkdir, mkdtemp, readFile, rm, writeFile } from "node:fs/promises";
import os from "node:os";
import path from "node:path";
import { promisify } from "node:util";
import { fileURLToPath } from "node:url";
const execFileAsync = promisify(execFile);
const repositoryRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), ".");
const packageJsonPath = path.join(repositoryRoot, "package.json");
function usage() {
return `Usage: node create-web-release.mjs [options]
Build and pack the browser package using the version of the root Cargo package.
Options:
--skip-build Pack the existing dist/ and wasm/pkg/ artifacts
--output-dir <path> Write the archive to this directory (default: repository root)
--help Show this help
`;
}
function parseArguments(arguments_) {
const options = {
outputDir: repositoryRoot,
skipBuild: false,
};
for (let index = 0; index < arguments_.length; index += 1) {
const argument = arguments_[index];
if (argument === "--help") {
options.help = true;
} else if (argument === "--skip-build") {
options.skipBuild = true;
} else if (argument === "--output-dir") {
const outputDir = arguments_[index + 1];
if (!outputDir || outputDir.startsWith("--")) {
throw new Error("--output-dir requires a directory path");
}
options.outputDir = path.resolve(repositoryRoot, outputDir);
index += 1;
} else if (argument.startsWith("--output-dir=")) {
const outputDir = argument.slice("--output-dir=".length);
if (!outputDir) {
throw new Error("--output-dir requires a directory path");
}
options.outputDir = path.resolve(repositoryRoot, outputDir);
} else {
throw new Error(`Unknown option: ${argument}`);
}
}
return options;
}
async function readJson(filePath) {
const source = await readFile(filePath, "utf8");
try {
return JSON.parse(source);
} catch (error) {
throw new Error(`Invalid JSON in ${path.relative(repositoryRoot, filePath)}`, {
cause: error,
});
}
}
async function run(command, arguments_, options = {}) {
const renderedArguments = arguments_.map((argument) => JSON.stringify(argument)).join(" ");
console.log(`\n> ${command}${renderedArguments ? ` ${renderedArguments}` : ""}`);
await new Promise((resolve, reject) => {
const child = spawn(command, arguments_, {
cwd: options.cwd ?? repositoryRoot,
env: options.env ?? process.env,
stdio: "inherit",
});
child.once("error", (error) => {
reject(new Error(`Failed to run ${command}: ${error.message}`, { cause: error }));
});
child.once("exit", (code, signal) => {
if (code === 0) {
resolve();
return;
}
const reason = signal ? `signal ${signal}` : `exit code ${code}`;
reject(new Error(`${command} failed with ${reason}`));
});
});
}
async function readCargoVersion() {
let stdout;
try {
({ stdout } = await execFileAsync(
"cargo",
[
"metadata",
"--no-deps",
"--format-version",
"1",
"--manifest-path",
path.join(repositoryRoot, "Cargo.toml"),
],
{ cwd: repositoryRoot, maxBuffer: 1024 * 1024 },
));
} catch (error) {
throw new Error(`Unable to read the root Cargo package version: ${error.message}`, {
cause: error,
});
}
let metadata;
try {
metadata = JSON.parse(stdout);
} catch (error) {
throw new Error("cargo metadata returned invalid JSON", { cause: error });
}
const rootPackage = metadata.packages?.find((packageMetadata) => packageMetadata.name === "mtp");
if (!rootPackage || typeof rootPackage.version !== "string") {
throw new Error("The root Cargo package named 'mtp' was not found");
}
return rootPackage.version;
}
function packageRelativePath(entry) {
if (typeof entry !== "string" || entry.length === 0) {
throw new Error("package.json files entries must be non-empty strings");
}
const relativePath = entry.replace(/\/$/, "");
if (
!relativePath ||
path.isAbsolute(relativePath) ||
relativePath.split(/[\\/]/u).includes("..") ||
relativePath.includes("*")
) {
throw new Error(`Unsupported package file entry: ${entry}`);
}
return relativePath;
}
async function copyPackageFiles(stageRoot, packageJson) {
if (!Array.isArray(packageJson.files)) {
throw new Error("package.json must declare a files array for Web releases");
}
for (const entry of packageJson.files) {
const relativePath = packageRelativePath(entry);
const sourcePath = path.join(repositoryRoot, relativePath);
const destinationPath = path.join(stageRoot, relativePath);
try {
await access(sourcePath);
} catch (error) {
throw new Error(`Release file is missing: ${relativePath}`, { cause: error });
}
await mkdir(path.dirname(destinationPath), { recursive: true });
await cp(sourcePath, destinationPath, { recursive: true });
}
}
async function createRelease({ outputDir, packageJson, version }) {
const stageRoot = await mkdtemp(path.join(os.tmpdir(), "mtp-web-release-"));
const stagedPackageJson = {
...packageJson,
version,
};
try {
await writeFile(
path.join(stageRoot, "package.json"),
`${JSON.stringify(stagedPackageJson, null, 2)}\n`,
);
await copyPackageFiles(stageRoot, packageJson);
const stagedWasmPackagePath = path.join(stageRoot, "wasm", "pkg", "package.json");
const stagedWasmPackageJson = await readJson(stagedWasmPackagePath);
stagedWasmPackageJson.version = version;
await writeFile(
stagedWasmPackagePath,
`${JSON.stringify(stagedWasmPackageJson, null, 2)}\n`,
);
await mkdir(outputDir, { recursive: true });
const archiveName = `${packageJson.name}-${version}.tgz`;
const archivePath = path.join(outputDir, archiveName);
await rm(archivePath, { force: true });
await run("npm", ["pack", "--pack-destination", outputDir], { cwd: stageRoot });
try {
await access(archivePath);
} catch (error) {
throw new Error(`npm pack did not create ${archiveName}`, { cause: error });
}
return archivePath;
} finally {
await rm(stageRoot, { recursive: true, force: true });
}
}
async function main() {
const options = parseArguments(process.argv.slice(2));
if (options.help) {
console.log(usage());
return;
}
const packageJson = await readJson(packageJsonPath);
if (packageJson.name !== "mtp") {
throw new Error("package.json must describe the 'mtp' Web package");
}
const version = await readCargoVersion();
console.log(`Using Cargo package version ${version}`);
if (!options.skipBuild) {
await run("pnpm", ["run", "clean"]);
await run("pnpm", ["run", "build"]);
}
const archivePath = await createRelease({
outputDir: options.outputDir,
packageJson,
version,
});
console.log(`\nCreated ${path.relative(repositoryRoot, archivePath) || archivePath}`);
}
main().catch((error) => {
console.error(`\n${error.message}`);
process.exitCode = 1;
});

View file

@ -129,7 +129,7 @@ impl<'a> MultiEncryptedMessageRef<'a> {
&self.bytes[self.ciphertext_start..] &self.bytes[self.ciphertext_start..]
} }
pub fn to_owned(&self) -> MultiEncryptedMessage { pub fn to_owned(self) -> MultiEncryptedMessage {
let recipients = (0..self.count) let recipients = (0..self.count)
.filter_map(|index| { .filter_map(|index| {
let (kem_ciphertext, encrypted_key) = self.recipient(index)?; let (kem_ciphertext, encrypted_key) = self.recipient(index)?;

View file

@ -3,7 +3,7 @@ use std::time::{Duration, Instant};
use mtp::client::MTPConnection; use mtp::client::MTPConnection;
use mtp::codec::{ use mtp::codec::{
CommunicationType, DataType, DataValue, ProtectionPolicy, ProtectedMessageBuilder, CommunicationType, DataType, DataValue, ProtectionPolicy, ProtectedMessageBuilder,
ProtectionPurpose, SealedRelayBuilder, SignaturePolicy, TypeMap, ProtectionPurpose, RelayOpenOptions, SealedRelayBuilder, SignaturePolicy, TypeMap,
open_relay_content_with_limits_without_replay, open_relay_content_with_limits_without_replay,
open_relay_metadata_without_replay, open_relay_metadata_without_replay,
}; };
@ -165,17 +165,17 @@ pub async fn send_sealed_relay(
&final_recipient_keyring, &final_recipient_keyring,
signer_id, signer_id,
&signer_keyring.public_key_bundle(), &signer_keyring.public_key_bundle(),
RELAY_SIGNATURE_POLICY, RelayOpenOptions::new(RELAY_SIGNATURE_POLICY),
)?; )?;
let application_metadata = metadata let application_metadata = metadata
.metadata() .metadata()
.ok_or("forwarded relay metadata was missing")?; .ok_or("forwarded relay metadata was missing")?;
let content = open_relay_content_with_limits_without_replay( let content = open_relay_content_with_limits_without_replay(
&metadata, &metadata,
&final_recipient_keyring, &[&final_recipient_keyring],
&signer_keyring.public_key_bundle(), &[signer_keyring.public_key_bundle()],
FINAL_RECIPIENT_ID, Some(FINAL_RECIPIENT_ID),
RELAY_SIGNATURE_POLICY, RelayOpenOptions::new(RELAY_SIGNATURE_POLICY),
)?; )?;
if content.message_type != "ProtectedMessage" { if content.message_type != "ProtectedMessage" {
return Err(format!("unexpected relay message type: {}", content.message_type).into()); return Err(format!("unexpected relay message type: {}", content.message_type).into());

View file

@ -2,7 +2,8 @@ use std::collections::HashMap;
use mtp::codec::{ use mtp::codec::{
CommunicationType, CommunicationValue, DataType, DataTypeId, DataValue, InMemoryReplayGuard, CommunicationType, CommunicationValue, DataType, DataTypeId, DataValue, InMemoryReplayGuard,
ProtectedOpenOptions, ProtectionPolicy, ProtectionPurpose, SignaturePolicy, TypeMap, ProtectedOpenOptions, ProtectionPolicy, ProtectionPurpose, RelayOpenOptions, SignaturePolicy,
TypeMap,
forward_relay_frame, open_protected_with_checked, forward_relay_frame, open_protected_with_checked,
open_relay_content_with_limits_without_replay, open_relay_content_with_limits_without_replay,
open_relay_metadata_with_checked, open_relay_metadata_with_checked,
@ -142,7 +143,7 @@ fn process_sealed_relay(
|signer_id| { |signer_id| {
resolve_signer_key(signer_id, registered_clients).map(|key| vec![key]) resolve_signer_key(signer_id, registered_clients).map(|key| vec![key])
}, },
SIGNATURE_POLICY, RelayOpenOptions::new(SIGNATURE_POLICY),
accepted_messages, accepted_messages,
) )
.map_err(|e| format!("metadata relay could not authenticate metadata: {e}"))?; .map_err(|e| format!("metadata relay could not authenticate metadata: {e}"))?;
@ -164,11 +165,11 @@ fn process_sealed_relay(
let content_result = open_relay_content_with_limits_without_replay( let content_result = open_relay_content_with_limits_without_replay(
&metadata, &metadata,
host_keyring, &[host_keyring],
&resolve_signer_key(metadata.signer_id(), registered_clients) &[resolve_signer_key(metadata.signer_id(), registered_clients)
.ok_or("metadata signer key disappeared")?, .ok_or("metadata signer key disappeared")?],
FINAL_RECIPIENT_ID, Some(FINAL_RECIPIENT_ID),
SIGNATURE_POLICY, RelayOpenOptions::new(SIGNATURE_POLICY),
); );
if content_result.is_ok() { if content_result.is_ok() {
return Err("metadata relay unexpectedly decrypted final-recipient content".into()); return Err("metadata relay unexpectedly decrypted final-recipient content".into());
@ -309,12 +310,22 @@ pub fn process_and_respond(
let signer_id = sig.as_signed().map(|signed| signed.signer_id); let signer_id = sig.as_signed().map(|signed| signed.signer_id);
if let Some(signer_id) = signer_id if let Some(signer_id) = signer_id
&& sig && sig
.verify(signer_id, pk_bundle, mtp::codec::ProtectionPurpose::from(2)) .verify_with_policy(
signer_id,
pk_bundle,
mtp::codec::ProtectionPurpose::from(2),
SIGNATURE_POLICY,
)
.is_ok() .is_ok()
{ {
let dv = sig let dv = sig
.clone() .clone()
.into_verified(signer_id, pk_bundle, mtp::codec::ProtectionPurpose::from(2)) .into_verified_with_policy(
signer_id,
pk_bundle,
mtp::codec::ProtectionPurpose::from(2),
SIGNATURE_POLICY,
)
.ok(); .ok();
if let Some(entries) = dv.and_then(|value| value.as_container()) { if let Some(entries) = dv.and_then(|value| value.as_container()) {
println!(" Verified SignedPayload: {:?}", entries); println!(" Verified SignedPayload: {:?}", entries);
@ -335,16 +346,22 @@ pub fn process_and_respond(
if let Ok(opened) = secure.decrypt(host_keyring, mtp::codec::ProtectionPurpose::from(4)) if let Ok(opened) = secure.decrypt(host_keyring, mtp::codec::ProtectionPurpose::from(4))
&& let Some(signed) = opened.as_signed() && let Some(signed) = opened.as_signed()
&& opened && opened
.verify( .verify_with_policy(
signed.signer_id, signed.signer_id,
pk_bundle, pk_bundle,
mtp::codec::ProtectionPurpose::from(3), mtp::codec::ProtectionPurpose::from(3),
SIGNATURE_POLICY,
) )
.is_ok() .is_ok()
{ {
let signer_id = signed.signer_id; let signer_id = signed.signer_id;
let dv = opened let dv = opened
.into_verified(signer_id, pk_bundle, mtp::codec::ProtectionPurpose::from(3)) .into_verified_with_policy(
signer_id,
pk_bundle,
mtp::codec::ProtectionPurpose::from(3),
SIGNATURE_POLICY,
)
.ok(); .ok();
if let Some(entries) = dv.and_then(|value| value.as_container()) { if let Some(entries) = dv.and_then(|value| value.as_container()) {
println!(" Verified SecurePayload: {:?}", entries); println!(" Verified SecurePayload: {:?}", entries);

View file

@ -1,6 +1,5 @@
{ {
description = "MTP - Methanium Transport Protocol"; description = "MTP - Methanium Transport Protocol";
inputs = { inputs = {
nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable"; nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
rust-overlay.url = "github:oxalica/rust-overlay"; rust-overlay.url = "github:oxalica/rust-overlay";

View file

@ -227,10 +227,11 @@ impl AuthenticationAttemptLimiter for InMemoryAuthenticationAttemptLimiter {
} }
for key in keys { for key in keys {
if !attempts.contains_key(&key) && attempts.len() >= self.max_keys { if !attempts.contains_key(&key)
if let Some(oldest) = attempts.keys().next().cloned() { && attempts.len() >= self.max_keys
attempts.remove(&oldest); && let Some(oldest) = attempts.keys().next().cloned()
} {
attempts.remove(&oldest);
} }
attempts.entry(key).or_default().push_back(now); attempts.entry(key).or_default().push_back(now);
} }

View file

@ -58,7 +58,8 @@
"build:wasm": "MTP_TYPE_MAPS=$PWD/example-type-maps.yaml wasm-pack build wasm --target web --out-dir pkg --release && rm -f wasm/pkg/.gitignore", "build:wasm": "MTP_TYPE_MAPS=$PWD/example-type-maps.yaml wasm-pack build wasm --target web --out-dir pkg --release && rm -f wasm/pkg/.gitignore",
"build:ts": "rm -rf dist && tsc", "build:ts": "rm -rf dist && tsc",
"build": "pnpm run build:wasm && pnpm run build:ts", "build": "pnpm run build:wasm && pnpm run build:ts",
"pack": "pnpm run clean && pnpm run build && pnpm pack", "pack": "pnpm run release:web",
"release:web": "node create-web-release.mjs",
"build:all": "nix run .#build-all", "build:all": "nix run .#build-all",
"dup": "jscpd --pattern '**/*.{rs,ts}' --ignore 'target/**' --ignore 'wasm/pkg/**' --ignore '.git/**' --min-lines 8 --min-tokens 80 --threshold 4 --reporters console --noTips .", "dup": "jscpd --pattern '**/*.{rs,ts}' --ignore 'target/**' --ignore 'wasm/pkg/**' --ignore '.git/**' --min-lines 8 --min-tokens 80 --threshold 4 --reporters console --noTips .",
"test:e2e": "tsc && node test/e2ee.mjs", "test:e2e": "tsc && node test/e2ee.mjs",

View file

@ -1460,9 +1460,11 @@ mod tests {
#[test] #[test]
fn runtime_policy_normalizes_zero_channel_and_task_limits() { fn runtime_policy_normalizes_zero_channel_and_task_limits() {
let mut policy = Policy::default(); let policy = Policy {
policy.receiver_queue_capacity = 0; receiver_queue_capacity: 0,
policy.max_concurrent_stream_tasks = 0; max_concurrent_stream_tasks: 0,
..Policy::default()
};
let runtime = RuntimePolicy::from_public(&policy); let runtime = RuntimePolicy::from_public(&policy);