mirror of
https://github.com/rustfs/rustfs.git
synced 2026-09-06 03:59:14 +00:00
fix(connect): preserve signature error classification
This commit is contained in:
@@ -271,6 +271,21 @@ struct SignedDocument {
|
||||
signature: DocumentSignature,
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
struct UnverifiedSignedDocument {
|
||||
bytes: String,
|
||||
signature: UnverifiedDocumentSignature,
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
struct UnverifiedDocumentSignature {
|
||||
algorithm: Option<serde_json::Value>,
|
||||
key_id: Option<serde_json::Value>,
|
||||
value: Option<serde_json::Value>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
struct DocumentSignature {
|
||||
@@ -364,7 +379,8 @@ impl OfflineEnrollment {
|
||||
now_unix: i64,
|
||||
root: EnrollmentRoot,
|
||||
) -> Result<VerifiedChallenge, EnrollmentError> {
|
||||
let envelope: SignedDocument = serde_json::from_slice(document).map_err(|_| EnrollmentError::MalformedDocument)?;
|
||||
let envelope: UnverifiedSignedDocument =
|
||||
serde_json::from_slice(document).map_err(|_| EnrollmentError::MalformedDocument)?;
|
||||
|
||||
// Decode the exact transmitted octets before reading any routing field.
|
||||
// Standard padded base64 is canonical in this protocol: accepting an
|
||||
@@ -385,7 +401,17 @@ impl OfflineEnrollment {
|
||||
// parsing any trust-link envelope or routing fields. Otherwise a
|
||||
// malformed link could mask a malformed artifact signature with
|
||||
// DOCUMENT_MALFORMED.
|
||||
let signature = decode_signature(&envelope.signature)?;
|
||||
let signature_member = |value: Option<serde_json::Value>| {
|
||||
value
|
||||
.and_then(|value| value.as_str().map(str::to_owned))
|
||||
.ok_or(EnrollmentError::SignatureMalformed)
|
||||
};
|
||||
let signature_document = DocumentSignature {
|
||||
algorithm: signature_member(envelope.signature.algorithm)?,
|
||||
key_id: signature_member(envelope.signature.key_id)?,
|
||||
value: signature_member(envelope.signature.value)?,
|
||||
};
|
||||
let signature = decode_signature(&signature_document)?;
|
||||
|
||||
let first = routing.trust_chain.first().ok_or(EnrollmentError::MalformedDocument)?;
|
||||
let first_bytes = first
|
||||
@@ -413,7 +439,7 @@ impl OfflineEnrollment {
|
||||
// Step 6. The detached signature must name the same chained key whose
|
||||
// public key verifies it. A different well-formed key id is a signature
|
||||
// failure, not an opportunity to ignore the binding.
|
||||
if envelope.signature.key_id != routing.connect_key_id || !verifies(&connect_key, TAG_CHALLENGE, &bytes, &signature) {
|
||||
if signature_document.key_id != routing.connect_key_id || !verifies(&connect_key, TAG_CHALLENGE, &bytes, &signature) {
|
||||
return Err(EnrollmentError::SignatureInvalid);
|
||||
}
|
||||
|
||||
@@ -438,7 +464,7 @@ impl OfflineEnrollment {
|
||||
issued_at: challenge.issued_at,
|
||||
expires_at: challenge.expires_at,
|
||||
connect_key_id: challenge.connect_key_id,
|
||||
challenge_proof: envelope.signature.value,
|
||||
challenge_proof: signature_document.value,
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -357,6 +357,41 @@ fn every_challenge_accept_vector_verifies_and_exposes_the_signed_fields() {
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn malformed_top_level_signature_members_keep_the_frozen_reason() {
|
||||
let vector = accept_vector_named("challenge signed by a chained signing key under the pinned root");
|
||||
let now = unix(field(&vector, "evaluationTime"));
|
||||
|
||||
for (member, malformed) in [
|
||||
("algorithm", serde_json::json!(1)),
|
||||
("keyId", serde_json::Value::Null),
|
||||
("value", serde_json::json!([])),
|
||||
] {
|
||||
let mut document = vector["document"].clone();
|
||||
document["signature"][member] = malformed;
|
||||
|
||||
let error = OfflineEnrollment::verify_challenge(&envelope(&document), now)
|
||||
.expect_err(&format!("a malformed top-level signature {member} must be refused"));
|
||||
assert_eq!(
|
||||
error.reason(),
|
||||
"SIGNATURE_MALFORMED",
|
||||
"a malformed top-level signature {member} keeps the frozen classifier"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn duplicate_top_level_signature_members_are_refused() {
|
||||
let vector = accept_vector_named("challenge signed by a chained signing key under the pinned root");
|
||||
let document = String::from_utf8(envelope(&vector["document"])).expect("envelope is UTF-8 JSON");
|
||||
let duplicate = document.replacen("\"algorithm\":\"ES256\"", "\"algorithm\":\"ES384\",\"algorithm\":\"ES256\"", 1);
|
||||
assert_ne!(duplicate, document, "the accepted vector carries the expected algorithm");
|
||||
|
||||
let error = OfflineEnrollment::verify_challenge(duplicate.as_bytes(), unix(field(&vector, "evaluationTime")))
|
||||
.expect_err("a duplicate top-level signature member must be refused");
|
||||
assert_eq!(error.reason(), "DOCUMENT_MALFORMED");
|
||||
}
|
||||
|
||||
#[cfg(feature = "offline-enrollment-e2e-root")]
|
||||
#[test]
|
||||
fn e2e_root_is_fixed_and_disjoint_from_the_hosted_root() {
|
||||
|
||||
Reference in New Issue
Block a user