Preserve the top API service job nonce (#7879)

fix(connect): preserve top api job nonce
This commit is contained in:
Chris
2026-09-15 01:19:28 +08:00
committed by GitHub
parent 2f266191fd
commit 7b82e6c375
3 changed files with 38 additions and 6 deletions
+4 -3
View File
@@ -37,7 +37,7 @@ use super::{
ProfileProvenance, TOP_API_CAPABILITY, TOP_CLASSIFICATION, TOP_LOCKS_CAPABILITY, TOP_SCHEMA_VERSION, TopApiOperation,
TopCaptureLimits, TopCaptureRequest, TopCaptureScope, TopOutcome, capture_cpu_profile, capture_top_api, capture_top_locks,
encode_signed_profile_export, measure_drive, measure_network, runtime_network_peer_aliases, sign_drive_export,
sign_network_export, sign_top_export,
sign_network_export, sign_top_export, sign_top_export_with_nonce,
};
use crate::connect::DeviceIdentity;
@@ -505,7 +505,7 @@ pub async fn execute_diagnostic_job(
DiagnosticJobKind::PerformanceNetwork => {
execute_performance_network_job(envelope, nonce, identity, provenance, cancel).await
}
DiagnosticJobKind::TopApi => execute_top_api_job(envelope, identity, provenance, cancel).await,
DiagnosticJobKind::TopApi => execute_top_api_job(envelope, nonce, identity, provenance, cancel).await,
DiagnosticJobKind::TopLocks => execute_top_locks_job(envelope, identity, provenance, cancel).await,
}
}
@@ -765,6 +765,7 @@ async fn execute_profile_cpu_job(
async fn execute_top_api_job(
envelope: DiagnosticJobEnvelope,
nonce: [u8; 32],
identity: &DeviceIdentity,
provenance: ProfileProvenance,
cancel: &CancellationToken,
@@ -814,7 +815,7 @@ async fn execute_top_api_job(
artifact_bytes: None,
});
}
let export = sign_top_export(&request, &result, identity, cancel).map_err(top_export_failure)?;
let export = sign_top_export_with_nonce(&request, &result, identity, cancel, nonce).map_err(top_export_failure)?;
if export.archive_bytes.len() > usize::try_from(envelope.limits.max_output_bytes).unwrap_or(usize::MAX) {
return Err(DiagnosticJobError::LimitExceeded);
}
+1
View File
@@ -135,6 +135,7 @@ pub use schedule::{
DiagnosticCollectionPolicy, DiagnosticReceipt, DiagnosticScheduleError, DiagnosticScheduleRuntime, DiagnosticScheduleStatus,
ReceiptOutcome, run_local_environment_once, spawn_environment_schedule,
};
pub(crate) use top_api::sign_top_export_with_nonce;
pub use top_api::{
LocalTopConsent, MAX_TOP_DURATION, MAX_TOP_EXPORT_VALIDITY, SavedTopExport, SignedTopExport, TOP_API_CAPABILITY,
TOP_CLASSIFICATION, TOP_SCHEMA_VERSION, TopApiData, TopApiOperation, TopCaptureError, TopCaptureLimits, TopCaptureRequest,
+33 -3
View File
@@ -454,6 +454,26 @@ pub fn sign_top_export<T: Serialize>(
result: &TopResult<T>,
identity: &DeviceIdentity,
cancel: &CancellationToken,
) -> Result<SignedTopExport, TopCaptureError> {
sign_top_export_inner(request, result, identity, cancel, None)
}
pub(crate) fn sign_top_export_with_nonce<T: Serialize>(
request: &TopCaptureRequest,
result: &TopResult<T>,
identity: &DeviceIdentity,
cancel: &CancellationToken,
nonce: [u8; 32],
) -> Result<SignedTopExport, TopCaptureError> {
sign_top_export_inner(request, result, identity, cancel, Some(nonce))
}
fn sign_top_export_inner<T: Serialize>(
request: &TopCaptureRequest,
result: &TopResult<T>,
identity: &DeviceIdentity,
cancel: &CancellationToken,
supplied_nonce: Option<[u8; 32]>,
) -> Result<SignedTopExport, TopCaptureError> {
if !matches!(result.tool_id, "top.api" | "top.disk" | "top.locks" | "top.net" | "top.rpc") {
return Err(TopCaptureError::Result);
@@ -495,8 +515,13 @@ pub fn sign_top_export<T: Serialize>(
return Err(TopCaptureError::Expired);
}
let mut nonce = [0u8; 32];
SysRng.try_fill_bytes(&mut nonce).map_err(|_| TopCaptureError::Random)?;
let nonce = if let Some(nonce) = supplied_nonce {
nonce
} else {
let mut nonce = [0u8; 32];
SysRng.try_fill_bytes(&mut nonce).map_err(|_| TopCaptureError::Random)?;
nonce
};
let device_key_id = hex_lower(&Sha256::digest(identity.public_key_der()));
let envelope = DiagnosticEnvelope {
format_version: ENVELOPE_FORMAT,
@@ -1046,7 +1071,7 @@ mod tests {
let result = capture.await.expect("capture task").expect("top.api result");
assert_eq!(result.outcome, TopOutcome::Succeeded);
let data = result.data.expect("successful capture data");
let data = result.data.as_ref().expect("successful capture data");
assert_eq!(data.operation, TopApiOperation::GetObject);
assert_eq!(data.request_count, 2);
assert_eq!(data.error_count, 1);
@@ -1062,6 +1087,11 @@ mod tests {
"windowMillis"
]
);
let nonce = [7_u8; 32];
let export = sign_top_export_with_nonce(&request, &result, &DeviceIdentity::generate(), &CancellationToken::new(), nonce)
.expect("signed top.api export");
let envelope: serde_json::Value = serde_json::from_slice(&export.envelope_json).expect("diagnostic envelope");
assert_eq!(envelope["nonce"], URL_SAFE_NO_PAD.encode_to_string(nonce));
}
#[tokio::test]