Skip to content

Commit a85f021

Browse files
committed
ed25519: more involved testing that invalid/modified sigs fail
1 parent bae0469 commit a85f021

File tree

3 files changed

+132
-13
lines changed

3 files changed

+132
-13
lines changed

common/src/auth.rs

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,5 @@
11
// user auth v1
22

3-
#[cfg(test)]
4-
use proptest_derive::Arbitrary;
53
use serde::{Deserialize, Serialize};
64
use thiserror::Error;
75

@@ -16,8 +14,11 @@ pub enum Error {
1614

1715
// TODO(phlip9): do we even need any signup fields?
1816
/// Sign up
19-
#[derive(Debug, PartialEq, Deserialize, Serialize)]
20-
#[cfg_attr(test, derive(Arbitrary))]
17+
#[derive(Debug, PartialEq, Eq, Deserialize, Serialize)]
18+
#[cfg_attr(
19+
all(test, not(target_env = "sgx")),
20+
derive(proptest_derive::Arbitrary)
21+
)]
2122
pub struct UserSignupRequest {
2223
pub display_name: Option<String>,
2324
pub email: Option<String>,
@@ -66,6 +67,7 @@ impl ed25519::Signable for UserSignupRequest {
6667
b"LEXE-REALM::UserSignupRequest";
6768
}
6869

70+
#[cfg(not(target_env = "sgx"))]
6971
#[cfg(test)]
7072
mod test {
7173
use super::*;

common/src/ed25519.rs

Lines changed: 125 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,7 @@ pub struct Signature([u8; 64]);
8686
/// `Signed<T>` is a "proof" that the signature `sig` on a [`Signable`] struct
8787
/// `T` was actually signed by `signer`.
8888
#[derive(Debug, PartialEq, Eq)]
89+
#[must_use]
8990
pub struct Signed<T: Signable> {
9091
signer: PublicKey,
9192
sig: Signature,
@@ -435,7 +436,7 @@ impl PublicKey {
435436

436437
/// Like [`ed25519::verify_signed_struct`](verify_signed_struct) but only
437438
/// allows signatures produced by this `ed25519::PublicKey`.
438-
pub fn verify_signed_struct<'msg, T: Signable + Deserialize<'msg>>(
439+
pub fn verify_self_signed_struct<'msg, T: Signable + Deserialize<'msg>>(
439440
&self,
440441
serialized: &'msg [u8],
441442
) -> Result<Signed<T>, Error> {
@@ -702,19 +703,36 @@ fn deserialize_pkcs8(bytes: &[u8]) -> Option<(&[u8; 32], &[u8; 32])> {
702703
#[cfg(test)]
703704
mod test {
704705
use proptest::arbitrary::any;
705-
use proptest::proptest;
706706
use proptest::strategy::Strategy;
707+
use proptest::{prop_assume, proptest};
708+
use proptest_derive::Arbitrary;
707709

708710
use super::*;
709711

710712
fn arb_seed() -> impl Strategy<Value = [u8; 32]> {
711-
any::<[u8; 32]>()
713+
any::<[u8; 32]>().no_shrink()
712714
}
713715

714716
fn arb_key_pair() -> impl Strategy<Value = KeyPair> {
715717
arb_seed().prop_map(|seed| KeyPair::from_seed(&seed))
716718
}
717719

720+
#[derive(Arbitrary, Deserialize, Serialize)]
721+
struct SignableBytes(Vec<u8>);
722+
723+
impl Signable for SignableBytes {
724+
const DOMAIN_SEPARATOR_STR: &'static [u8] =
725+
b"LEXE-REALM::SignableBytes";
726+
}
727+
728+
impl fmt::Debug for SignableBytes {
729+
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
730+
f.debug_tuple("SignableBytes")
731+
.field(&hex::display(&self.0))
732+
.finish()
733+
}
734+
}
735+
718736
#[test]
719737
fn test_serde_pkcs8_roundtrip() {
720738
proptest!(|(seed in arb_seed())| {
@@ -765,6 +783,103 @@ mod test {
765783
pubkey.verify_raw(&msg, &sig2).unwrap();
766784
}
767785

786+
// truncating the signed struct should cause the verification to fail.
787+
#[test]
788+
fn test_reject_truncated_sig() {
789+
proptest!(|(key_pair in arb_key_pair(), msg in any::<SignableBytes>())| {
790+
let pubkey = key_pair.public_key();
791+
792+
let (sig, _) = key_pair.sign_struct(&msg).unwrap();
793+
let _ = pubkey
794+
.verify_self_signed_struct::<SignableBytes>(&sig)
795+
.unwrap();
796+
797+
for trunc_len in 0..SIGNED_STRUCT_OVERHEAD {
798+
pubkey
799+
.verify_self_signed_struct::<SignableBytes>(&sig[..trunc_len])
800+
.unwrap_err();
801+
pubkey
802+
.verify_self_signed_struct::<SignableBytes>(&sig[(trunc_len+1)..])
803+
.unwrap_err();
804+
}
805+
});
806+
}
807+
808+
// inserting some random bytes into the signed struct should cause the
809+
// sig verification to fail
810+
#[test]
811+
fn test_reject_pad_sig() {
812+
let cfg = proptest::test_runner::Config::with_cases(50);
813+
proptest!(cfg, |(
814+
key_pair in arb_key_pair(),
815+
msg in any::<SignableBytes>(),
816+
padding in any::<Vec<u8>>(),
817+
)| {
818+
prop_assume!(!padding.is_empty());
819+
820+
let pubkey = key_pair.public_key();
821+
822+
let (sig, _) = key_pair.sign_struct(&msg).unwrap();
823+
let _ = pubkey
824+
.verify_self_signed_struct::<SignableBytes>(&sig)
825+
.unwrap();
826+
827+
let mut sig2: Vec<u8> = Vec::with_capacity(sig.len() + padding.len());
828+
829+
for idx in 0..=sig.len() {
830+
let (left, right) = sig.split_at(idx);
831+
832+
// sig2 := left || padding || right
833+
834+
sig2.clear();
835+
sig2.extend_from_slice(left);
836+
sig2.extend_from_slice(&padding);
837+
sig2.extend_from_slice(right);
838+
839+
pubkey
840+
.verify_self_signed_struct::<SignableBytes>(&sig2)
841+
.unwrap_err();
842+
}
843+
});
844+
}
845+
846+
// flipping some random bits in the signed struct should cause the
847+
// verification to fail.
848+
#[test]
849+
fn test_reject_modified_sig() {
850+
let arb_mutation = any::<Vec<u8>>()
851+
.prop_filter("can't be empty or all zeroes", |m| {
852+
!m.is_empty() && !m.iter().all(|x| x == &0u8)
853+
});
854+
855+
proptest!(|(
856+
key_pair in arb_key_pair(),
857+
msg in any::<SignableBytes>(),
858+
mut_offset in any::<usize>(),
859+
mut mutation in arb_mutation,
860+
)| {
861+
let pubkey = key_pair.public_key();
862+
863+
let (mut sig, _) = key_pair.sign_struct(&msg).unwrap();
864+
865+
mutation.truncate(sig.len());
866+
prop_assume!(!mutation.is_empty() && !mutation.iter().all(|x| x == &0));
867+
868+
let _ = pubkey
869+
.verify_self_signed_struct::<SignableBytes>(&sig)
870+
.unwrap();
871+
872+
// xor in the mutation bytes to the signature to modify it. any
873+
// modified bit should cause the verification to fail.
874+
for (idx_mut, m) in mutation.into_iter().enumerate() {
875+
let idx_sig = idx_mut.wrapping_add(mut_offset) % sig.len();
876+
sig[idx_sig] ^= m;
877+
}
878+
879+
pubkey.verify_self_signed_struct::<SignableBytes>(&sig).unwrap_err();
880+
});
881+
}
882+
768883
#[test]
769884
fn test_sign_verify() {
770885
proptest!(|(key_pair in arb_key_pair(), msg in any::<Vec<u8>>())| {
@@ -797,15 +912,17 @@ mod test {
797912

798913
proptest!(|(key_pair in arb_key_pair(), foo in arb_foo())| {
799914
let signer = key_pair.public_key();
800-
let (serialized, signed_foo) = key_pair.sign_struct::<Foo>(&foo).unwrap();
915+
let (serialized, signed_foo) =
916+
key_pair.sign_struct::<Foo>(&foo).unwrap();
801917

802-
let signed_foo2 = signer.verify_signed_struct::<Foo>(&serialized).unwrap();
918+
let signed_foo2 =
919+
signer.verify_self_signed_struct::<Foo>(&serialized).unwrap();
803920
assert_eq!(signed_foo, signed_foo2.as_ref());
804921

805-
// trying to verify as another struct with the same serialization
806-
// should be prevented by domain separation
922+
// trying to verify signature as another type with a valid
923+
// serialization is prevented by domain separation.
807924

808-
signer.verify_signed_struct::<Bar>(&serialized).unwrap_err();
925+
signer.verify_self_signed_struct::<Bar>(&serialized).unwrap_err();
809926
});
810927
}
811928
}

common/src/test_utils.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ where
2828
let pubkey = key_pair.public_key();
2929

3030
let (ser_value, signed_value) = key_pair.sign_struct(&value).unwrap();
31-
let signed_value2 = pubkey.verify_signed_struct(&ser_value).unwrap();
31+
let signed_value2 = pubkey.verify_self_signed_struct(&ser_value).unwrap();
3232
let (ser_value2, _) = key_pair.sign_struct(signed_value2.inner()).unwrap();
3333

3434
assert_eq!(signed_value, signed_value2.as_ref());

0 commit comments

Comments
 (0)