1#![no_std]
2#![doc = "Crypto-agile envelope metadata for BCX."]
3
4mod envelope;
5mod error;
6
7pub use envelope::{
8 AlgorithmPolicy, HybridVerified, HybridVerifier, SignatureAlgorithm, SignatureEnvelope,
9 SignedEnvelope, Verifier,
10};
11pub use error::VerificationError;
12
13#[cfg(test)]
14mod tests {
15 use super::*;
16 use bcx_core::Digest;
17 use bcx_wire::WireLimits;
18
19 #[test]
20 fn empty_signature_is_rejected() {
21 assert_eq!(
22 SignatureEnvelope::new(
23 Digest::new([1; Digest::LEN]),
24 SignatureAlgorithm::Ed25519,
25 &[],
26 WireLimits::DEVELOPMENT,
27 ),
28 Err(VerificationError::EmptySignature)
29 );
30 }
31
32 #[test]
33 fn wrong_signature_length_is_rejected() {
34 assert_eq!(
35 SignatureEnvelope::new(
36 Digest::new([1; Digest::LEN]),
37 SignatureAlgorithm::Ed25519,
38 &[7; 63],
39 WireLimits::DEVELOPMENT,
40 ),
41 Err(VerificationError::InvalidSignature)
42 );
43 }
44
45 #[test]
46 fn hybrid_signature_split_uses_documented_layout() {
47 let signature = [5; SignatureAlgorithm::HYBRID_ED25519_ML_DSA_65_SIGNATURE_LEN];
48 let split = SignatureAlgorithm::HybridEd25519MlDsa65.split_hybrid(&signature);
49
50 assert_eq!(
51 split.map(|(ed25519, ml_dsa)| (ed25519.len(), ml_dsa.len())),
52 Some((
53 SignatureAlgorithm::ED25519_SIGNATURE_LEN,
54 SignatureAlgorithm::ML_DSA_65_SIGNATURE_LEN,
55 ))
56 );
57 assert_eq!(SignatureAlgorithm::Ed25519.split_hybrid(&signature), None);
58 }
59
60 #[test]
61 fn policy_rejects_unadmitted_algorithm_before_verifier() -> Result<(), VerificationError> {
62 struct RejectingVerifier;
63
64 impl HybridVerifier for RejectingVerifier {
65 fn verify_ed25519(
66 &self,
67 _ed25519_signature: &[u8],
68 _canonical_payload: &[u8],
69 ) -> Result<(), VerificationError> {
70 Err(VerificationError::InvalidSignature)
71 }
72
73 fn verify_ml_dsa_65(
74 &self,
75 _ml_dsa_65_signature: &[u8],
76 _canonical_payload: &[u8],
77 ) -> Result<(), VerificationError> {
78 Err(VerificationError::InvalidSignature)
79 }
80 }
81
82 impl Verifier for RejectingVerifier {
83 fn verify(
84 &self,
85 _envelope: &SignatureEnvelope<'_>,
86 _canonical_payload: &[u8],
87 ) -> Result<(), VerificationError> {
88 Err(VerificationError::InvalidSignature)
89 }
90 }
91
92 let signature = [1; 64];
93 let envelope = SignedEnvelope::new(
94 (),
95 SignatureEnvelope::new(
96 Digest::new([1; Digest::LEN]),
97 SignatureAlgorithm::Ed25519,
98 &signature,
99 WireLimits::DEVELOPMENT,
100 )?,
101 );
102 let policy = AlgorithmPolicy::new(&[SignatureAlgorithm::MlDsa65]);
103
104 assert_eq!(
105 envelope.verify_detached_bytes(
106 &RejectingVerifier,
107 &policy,
108 b"payload",
109 WireLimits::DEVELOPMENT,
110 ),
111 Err(VerificationError::AlgorithmNotAdmitted)
112 );
113 Ok(())
114 }
115
116 #[test]
117 fn detached_payload_is_bounded_by_wire_limits() -> Result<(), VerificationError> {
118 struct AcceptingVerifier;
119
120 impl HybridVerifier for AcceptingVerifier {
121 fn verify_ed25519(
122 &self,
123 _ed25519_signature: &[u8],
124 _canonical_payload: &[u8],
125 ) -> Result<(), VerificationError> {
126 Ok(())
127 }
128
129 fn verify_ml_dsa_65(
130 &self,
131 _ml_dsa_65_signature: &[u8],
132 _canonical_payload: &[u8],
133 ) -> Result<(), VerificationError> {
134 Ok(())
135 }
136 }
137
138 impl Verifier for AcceptingVerifier {
139 fn verify(
140 &self,
141 _envelope: &SignatureEnvelope<'_>,
142 _canonical_payload: &[u8],
143 ) -> Result<(), VerificationError> {
144 Ok(())
145 }
146 }
147
148 let signature = [1; 64];
149 let envelope = SignedEnvelope::new(
150 (),
151 SignatureEnvelope::new(
152 Digest::new([1; Digest::LEN]),
153 SignatureAlgorithm::Ed25519,
154 &signature,
155 WireLimits::DEVELOPMENT,
156 )?,
157 );
158 let payload = [0; 65];
159 let limits =
160 WireLimits::new(64, 1, 1, 1).map_err(|_| VerificationError::InvalidSignature)?;
161 let policy = AlgorithmPolicy::new(&[SignatureAlgorithm::Ed25519]);
162
163 assert_eq!(
164 envelope.verify_detached_bytes(&AcceptingVerifier, &policy, &payload, limits),
165 Err(VerificationError::PayloadTooLarge)
166 );
167 Ok(())
168 }
169
170 #[test]
171 fn signature_envelope_debug_redacts_signature_bytes() -> Result<(), VerificationError> {
172 extern crate std;
173 use std::{format, string::String};
174
175 let signature = [7; SignatureAlgorithm::ED25519_SIGNATURE_LEN];
176 let envelope = SignatureEnvelope::new(
177 Digest::new([1; Digest::LEN]),
178 SignatureAlgorithm::Ed25519,
179 &signature,
180 WireLimits::DEVELOPMENT,
181 )?;
182
183 assert_eq!(
184 format!("{envelope:?}"),
185 String::from(
186 "SignatureEnvelope { key_id: Digest(..), algorithm: Ed25519, signature: [64 bytes] }"
187 )
188 );
189 Ok(())
190 }
191
192 #[test]
193 fn hybrid_verification_requires_both_components() -> Result<(), VerificationError> {
194 struct PartialHybridVerifier;
195
196 impl HybridVerifier for PartialHybridVerifier {
197 fn verify_ed25519(
198 &self,
199 ed25519_signature: &[u8],
200 _canonical_payload: &[u8],
201 ) -> Result<(), VerificationError> {
202 if ed25519_signature.len() == SignatureAlgorithm::ED25519_SIGNATURE_LEN {
203 Ok(())
204 } else {
205 Err(VerificationError::InvalidSignature)
206 }
207 }
208
209 fn verify_ml_dsa_65(
210 &self,
211 _ml_dsa_65_signature: &[u8],
212 _canonical_payload: &[u8],
213 ) -> Result<(), VerificationError> {
214 Err(VerificationError::InvalidSignature)
215 }
216 }
217
218 impl Verifier for PartialHybridVerifier {
219 fn verify(
220 &self,
221 _envelope: &SignatureEnvelope<'_>,
222 _canonical_payload: &[u8],
223 ) -> Result<(), VerificationError> {
224 Ok(())
225 }
226 }
227
228 let signature = [9; SignatureAlgorithm::HYBRID_ED25519_ML_DSA_65_SIGNATURE_LEN];
229 let envelope = SignedEnvelope::new(
230 (),
231 SignatureEnvelope::new(
232 Digest::new([1; Digest::LEN]),
233 SignatureAlgorithm::HybridEd25519MlDsa65,
234 &signature,
235 WireLimits::DEVELOPMENT,
236 )?,
237 );
238 let policy = AlgorithmPolicy::new(&[SignatureAlgorithm::HybridEd25519MlDsa65]);
239
240 assert_eq!(
241 envelope.verify_detached_bytes(
242 &PartialHybridVerifier,
243 &policy,
244 b"payload",
245 WireLimits::DEVELOPMENT,
246 ),
247 Err(VerificationError::InvalidSignature)
248 );
249 Ok(())
250 }
251}