Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
97 changes: 97 additions & 0 deletions crates/identity/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -847,6 +847,103 @@ mod tests {
assert!(result.is_err(), "tampered profile must not verify");
}

/// Issue #424: an `EndpointId` parsed from a string that is not valid hex
/// (and not a valid z32-encoded key) must fail with a parse error rather
/// than silently producing a bogus key. `EndpointId` aliases iroh's
/// `PublicKey`, whose `FromStr` impl rejects garbage input.
#[test]
fn endpoint_id_rejects_malformed_hex() {
use std::str::FromStr;

// Not hex, not z32, just a clearly-wrong string.
let result = EndpointId::from_str("not-a-valid-key!!!");
assert!(
result.is_err(),
"malformed string must not parse to an EndpointId, got: {result:?}"
);

// 64 chars but contains non-hex characters — should still fail.
let bad_hex = "z".repeat(64);
let result = EndpointId::from_str(&bad_hex);
assert!(
result.is_err(),
"non-hex 64-char string must not parse, got: {result:?}"
);

// Wrong length hex (too short).
let result = EndpointId::from_str("deadbeef");
assert!(
result.is_err(),
"short hex must not parse to an EndpointId, got: {result:?}"
);
}

/// Issue #424: feeding random / garbage bytes to `unpack_profile`
/// (which deserializes via `willow_transport::unpack`, i.e. bincode)
/// must surface as `IdentityError::Serde`, not a panic and not a
/// silent default-constructed profile.
#[test]
fn profile_decode_rejects_invalid_cbor() {
// Random-looking bytes that don't form a valid bincode-encoded
// `SignedMessage`. (The crate uses bincode under the hood, but
// the issue still names the test for historical reasons.)
let garbage: [u8; 16] = [
0xFF, 0x00, 0xDE, 0xAD, 0xBE, 0xEF, 0x12, 0x34, 0x56, 0x78, 0x9A, 0xBC, 0xDE, 0xF0,
0x42, 0x42,
];

let err = unpack_profile(&garbage).unwrap_err();
match err {
IdentityError::Serde(_) => {}
other => panic!("expected IdentityError::Serde for garbage bytes, got {other:?}"),
}

// Empty input must also be rejected as a serialization error
// (bincode can't parse an empty buffer into a `SignedMessage`).
let err = unpack_profile(&[]).unwrap_err();
match err {
IdentityError::Serde(_) => {}
other => panic!("expected IdentityError::Serde for empty bytes, got {other:?}"),
}
}

/// Issue #424: malformed serialized envelope bytes — truncated mid-record
/// or otherwise non-decodable — must be rejected by `unpack` with
/// `IdentityError::Serde`. Exercises the outer-envelope deserialization
/// path, distinct from a tampered-but-well-formed payload (which is
/// covered by `tampered_payload_fails_verification`).
#[test]
fn unpack_rejects_truncated_envelope_bytes() {
let alice = Identity::generate();
let signed = pack(&String::from("hello"), &alice).unwrap();

// Truncate to roughly the first quarter of the envelope so that
// bincode can't reconstruct the `SignedMessage` framing
// (length prefixes / Vec contents will run off the end).
let truncated = &signed[..signed.len() / 4];
let err = unpack::<String>(truncated).unwrap_err();
match err {
IdentityError::Serde(_) => {}
other => panic!("expected IdentityError::Serde for truncated bytes, got {other:?}"),
}

// A `SignedMessage` whose `public_key` field is the wrong length
// must reach the verify path and fail with `PublicKeyDecode`,
// proving the signature path's malformed-bytes rejection works
// for well-framed but cryptographically-invalid inputs.
let bogus = SignedMessage {
public_key: vec![0u8; 7], // not 32 bytes
signature: vec![0u8; 64],
payload: willow_transport::pack(&String::from("anything")).unwrap(),
};
let bytes = willow_transport::pack(&bogus).unwrap();
let err = unpack::<String>(&bytes).unwrap_err();
match err {
IdentityError::PublicKeyDecode(_) => {}
other => panic!("expected IdentityError::PublicKeyDecode for short pk, got {other:?}"),
}
}

/// A key file with fewer than 32 bytes causes `load_or_generate` to return
/// an error rather than panic.
#[test]
Expand Down