did_simple/crypto/
mod.rs

1//! Implementations of cryptographic operations
2
3// Re-exports
4#[cfg(feature = "random")]
5pub use rand_core;
6
7#[cfg(feature = "ed25519")]
8pub mod ed25519;
9
10/// The "context" for signing and verifying messages, which is used for domain
11/// separation of message signatures. The context can be of length
12/// `[Context::MIN_LEN..Context::MAX_LEN]`.
13///
14/// # Example
15///
16/// ```
17/// use did_simple::crypto::Context;
18/// const CTX: Context = Context::from_bytes("MySuperCoolProtocol".as_bytes());
19/// ```
20///
21/// # What is the purpose of this?
22///
23/// Messages signed using one context cannot be verified under a different
24/// context. This is important, because it prevents tricking someone into
25/// signing a message for one use case, and it getting reused for another use
26/// case.
27///
28/// # Can you give me an example of how not using a context can be bad?
29///
30/// Suppose that a scammer sends you a file and asks you to send it back to them
31/// signed, to prove to them that you got the message. You naively comply, only
32/// later to realize that the file you signed actually is a json message that
33/// authorizes your bank to send funds to the scammer. If you reused the same
34/// public key for sending messages as you do for authorizing bank transactions,
35/// you just got robbed.
36///
37/// If instead the application you were using signed that message with a
38/// "MySecureProtocolSendMessage" context, and your bank used "MySuperSafeBank",
39/// your bank would have rejected the message signature when the scammer tried
40/// to use it to authorize a funds transfer because the two contexts don't
41/// match.
42///
43/// # But I *really* need to not use a context for this specific case 🥺
44///
45/// Most of the signing algorithms' `VerifyingKey`s expose an `into_inner`
46/// method and reexport the cryptography crate they use. So you can just call
47/// the relevant signing functions yourself with the lower level crate.
48#[derive(Debug, Eq, PartialEq, Copy, Clone)]
49pub struct Context<'a>(&'a [u8]);
50
51/// Helper just to allow same value in macro contexts as const contexts
52macro_rules! ctx_len {
53	("max") => {
54		255
55	};
56	("min") => {
57		4
58	};
59}
60
61impl<'a> Context<'a> {
62	pub const MAX_LEN: usize = Self::max_len();
63	pub const MIN_LEN: usize = Self::min_len();
64
65	/// Panics if `value` is longer than [`Self::MAX_LEN`] or is 0.
66	pub const fn from_bytes(value: &'a [u8]) -> Self {
67		match Self::try_from_bytes(value) {
68			Ok(ctx) => ctx,
69			Err(err) => panic!("{}", err.const_display()),
70		}
71	}
72
73	pub const fn try_from_bytes(value: &'a [u8]) -> Result<Self, ContextError> {
74		let len = value.len();
75		if len < Self::MIN_LEN {
76			Err(ContextError::SliceTooShort)
77		} else if len > Self::MAX_LEN {
78			Err(ContextError::SliceTooLong(len))
79		} else {
80			Ok(Context(value))
81		}
82	}
83
84	const fn max_len() -> usize {
85		let result = ed25519_dalek::Context::<ed25519_dalek::VerifyingKey>::MAX_LENGTH;
86		assert!(result == ctx_len!("max"));
87		result
88	}
89
90	const fn min_len() -> usize {
91		let result = ctx_len!("min");
92		assert!(result <= Self::MAX_LEN);
93		result
94	}
95}
96
97impl<'a> TryFrom<&'a [u8]> for Context<'a> {
98	type Error = ContextError;
99
100	fn try_from(value: &'a [u8]) -> Result<Self, Self::Error> {
101		Self::try_from_bytes(value)
102	}
103}
104
105#[derive(thiserror::Error, Debug, Eq, PartialEq)]
106pub enum ContextError {
107	#[error(
108		"requires a slice of at least length {} but got a slice of length {0}",
109		Context::MIN_LEN
110	)]
111	SliceTooShort,
112	#[error(
113		"requires a slice of at most length {} but got a slice of length {0}",
114		Context::MAX_LEN
115	)]
116	SliceTooLong(usize),
117}
118
119impl ContextError {
120	const fn const_display(&self) -> &str {
121		match self {
122			Self::SliceTooShort => {
123				concat!("requires a slice of at least length ", ctx_len!("min"))
124			}
125			Self::SliceTooLong(_len) => {
126				concat!("requires a slice of at most length ", ctx_len!("max"))
127			}
128		}
129	}
130}
131
132#[cfg(test)]
133mod test {
134	use super::*;
135
136	#[test]
137	fn test_ctx_try_from() {
138		let valid = [
139			[0; Context::MIN_LEN].as_slice(),
140			[0; Context::MAX_LEN].as_slice(),
141		];
142		for s in valid {
143			assert_eq!(Context::from_bytes(s), Context::try_from_bytes(s).unwrap());
144		}
145
146		let too_short = [&[], [0; Context::MIN_LEN - 1].as_slice()];
147		for s in too_short {
148			assert_eq!(Context::try_from_bytes(s), Err(ContextError::SliceTooShort));
149			assert!(std::panic::catch_unwind(|| Context::from_bytes(s)).is_err());
150		}
151
152		let too_long = [
153			[0; Context::MAX_LEN + 1].as_slice(),
154			[0; Context::MAX_LEN + 2].as_slice(),
155		];
156		for s in too_long {
157			assert_eq!(
158				Context::try_from_bytes(s),
159				Err(ContextError::SliceTooLong(s.len()))
160			);
161			assert!(std::panic::catch_unwind(|| Context::from_bytes(s)).is_err());
162		}
163	}
164}