diff --git a/crates/identity/src/lib.rs b/crates/identity/src/lib.rs index 917d9a39..92b5cecc 100644 --- a/crates/identity/src/lib.rs +++ b/crates/identity/src/lib.rs @@ -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::(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::(&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]