gmcrypto_core/asn1/
ciphertext.rs1use alloc::vec::Vec;
35use crypto_bigint::U256;
36use subtle::ConstantTimeLess;
37
38use crate::sm2::curve::Fp;
39
40use super::{reader, writer};
41
42const HASH_LEN: usize = 32;
44
45#[derive(Clone, Debug)]
51pub struct Sm2Ciphertext {
52 pub x: U256,
54 pub y: U256,
56 pub hash: [u8; HASH_LEN],
58 pub ciphertext: Vec<u8>,
60}
61
62#[must_use]
64pub fn encode(ct: &Sm2Ciphertext) -> Vec<u8> {
65 let x_be = ct.x.to_be_bytes();
66 let y_be = ct.y.to_be_bytes();
67 let mut body = Vec::with_capacity(ct.ciphertext.len() + 80);
68 writer::write_integer(&mut body, &x_be);
69 writer::write_integer(&mut body, &y_be);
70 writer::write_octet_string(&mut body, &ct.hash);
71 writer::write_octet_string(&mut body, &ct.ciphertext);
72 let mut out = Vec::with_capacity(body.len() + 4);
73 writer::write_sequence(&mut out, &body);
74 out
75}
76
77#[must_use]
81pub fn decode(input: &[u8]) -> Option<Sm2Ciphertext> {
82 let (body, rest) = reader::read_sequence(input)?;
83 if !rest.is_empty() {
84 return None;
85 }
86 let (x, body) = read_field_element(body)?;
87 let (y, body) = read_field_element(body)?;
88 let (hash_bytes, body) = reader::read_octet_string(body)?;
89 let (ciphertext, body) = reader::read_octet_string(body)?;
90 if !body.is_empty() {
91 return None;
92 }
93 if hash_bytes.len() != HASH_LEN {
94 return None;
95 }
96 let mut hash = [0u8; HASH_LEN];
97 hash.copy_from_slice(hash_bytes);
98 Some(Sm2Ciphertext {
99 x,
100 y,
101 hash,
102 ciphertext: ciphertext.to_vec(),
103 })
104}
105
106fn read_field_element(input: &[u8]) -> Option<(U256, &[u8])> {
111 let (bytes, rest) = reader::read_integer(input)?;
112 if bytes.len() > 32 {
113 return None;
114 }
115 let mut padded = [0u8; 32];
116 padded[32 - bytes.len()..].copy_from_slice(bytes);
117 let value = U256::from_be_slice(&padded);
118 let in_field: bool = value.ct_lt(Fp::MODULUS.as_ref()).into();
122 if !in_field {
123 return None;
124 }
125 Some((value, rest))
126}
127
128#[cfg(test)]
129mod tests {
130 use super::*;
131
132 fn make_ct(ciphertext: Vec<u8>) -> Sm2Ciphertext {
133 Sm2Ciphertext {
134 x: U256::from_be_hex(
135 "1234567890ABCDEF1234567890ABCDEF1234567890ABCDEF1234567890ABCDEF",
136 ),
137 y: U256::from_be_hex(
138 "FEDCBA0987654321FEDCBA0987654321FEDCBA0987654321FEDCBA0987654321",
139 ),
140 hash: [0xa5u8; 32],
141 ciphertext,
142 }
143 }
144
145 fn wrap_sequence(body: &[u8]) -> Vec<u8> {
149 let mut out = Vec::new();
150 writer::write_sequence(&mut out, body);
151 out
152 }
153
154 #[test]
156 fn round_trip_short() {
157 let ct = make_ct(b"hello world".to_vec());
158 let der = encode(&ct);
159 let decoded = decode(&der).expect("decode round-trip");
160 assert_eq!(decoded.x, ct.x);
161 assert_eq!(decoded.y, ct.y);
162 assert_eq!(decoded.hash, ct.hash);
163 assert_eq!(decoded.ciphertext, ct.ciphertext);
164 }
165
166 #[test]
169 fn round_trip_x_high_bit_set() {
170 let mut ct = make_ct(b"x".to_vec());
171 ct.x =
172 U256::from_be_hex("FFEDCBA9876543210FEDCBA9876543210FEDCBA9876543210FEDCBA987654321");
173 let der = encode(&ct);
174 let decoded = decode(&der).expect("decode high-bit round-trip");
175 assert_eq!(decoded.x, ct.x);
176 }
177
178 #[test]
181 fn round_trip_medium_ciphertext_300_bytes() {
182 let mut payload = alloc::vec![0u8; 300];
183 for (i, b) in payload.iter_mut().enumerate() {
184 #[allow(clippy::cast_possible_truncation)]
185 {
186 *b = (i as u8).wrapping_mul(13);
187 }
188 }
189 let ct = make_ct(payload.clone());
190 let der = encode(&ct);
191 let decoded = decode(&der).expect("decode 300-byte round-trip");
192 assert_eq!(decoded.ciphertext, payload);
193 }
194
195 #[test]
198 fn round_trip_empty_ciphertext() {
199 let ct = make_ct(Vec::new());
200 let der = encode(&ct);
201 let decoded = decode(&der).expect("decode empty-ciphertext round-trip");
202 assert!(decoded.ciphertext.is_empty());
203 }
204
205 #[test]
207 fn rejects_malformed() {
208 assert!(decode(&[]).is_none(), "empty input");
209 assert!(decode(&[0x30]).is_none(), "truncated SEQUENCE header");
210 assert!(decode(&[0x31, 0x00]).is_none(), "wrong outer tag");
211 assert!(decode(&[0x30, 0x05, 0x02, 0x01, 0x01]).is_none());
213 }
214
215 #[test]
219 fn rejects_wrong_hash_length() {
220 let bad_hash = [0x55u8; 31];
222 let ciphertext = b"x";
223 let mut body = Vec::new();
224 writer::write_integer(&mut body, &[0x01]);
225 writer::write_integer(&mut body, &[0x02]);
226 writer::write_octet_string(&mut body, &bad_hash);
227 writer::write_octet_string(&mut body, ciphertext);
228 let der = wrap_sequence(&body);
229 assert!(
230 decode(&der).is_none(),
231 "31-byte HASH must be rejected; SM3 always produces 32 bytes"
232 );
233 }
234
235 #[test]
240 fn rejects_non_canonical_x_leading_zero() {
241 let mut body = Vec::new();
243 body.extend_from_slice(&[0x02, 0x02, 0x00, 0x01]); writer::write_integer(&mut body, &[0x02]); writer::write_octet_string(&mut body, &[0u8; 32]);
246 writer::write_octet_string(&mut body, b"");
247 let der = wrap_sequence(&body);
248 assert!(
249 decode(&der).is_none(),
250 "non-canonical 00-pad on x must be rejected"
251 );
252 }
253
254 #[test]
257 fn rejects_negative_y_encoding() {
258 let mut body = Vec::new();
259 writer::write_integer(&mut body, &[0x01]);
260 body.extend_from_slice(&[0x02, 0x01, 0x80]); writer::write_octet_string(&mut body, &[0u8; 32]);
262 writer::write_octet_string(&mut body, b"");
263 let der = wrap_sequence(&body);
264 assert!(decode(&der).is_none());
265 }
266
267 #[test]
270 fn rejects_trailing_bytes() {
271 let ct = make_ct(b"hi".to_vec());
272 let mut der = encode(&ct);
273 der.push(0xff); assert!(decode(&der).is_none());
275 }
276
277 #[test]
281 fn round_trip_x_zero() {
282 let mut ct = make_ct(b"z".to_vec());
283 ct.x = U256::ZERO;
284 let der = encode(&ct);
285 let decoded = decode(&der).expect("decode round-trip with x = 0");
286 assert_eq!(decoded.x, U256::ZERO);
287 assert_eq!(decoded.y, ct.y);
288 }
289
290 #[test]
296 fn rejects_x_at_or_above_p() {
297 let p = *Fp::MODULUS.as_ref();
299 let p_bytes = p.to_be_bytes();
300 let mut body = Vec::new();
301 writer::write_integer(&mut body, &p_bytes);
302 writer::write_integer(&mut body, &[0x01]);
303 writer::write_octet_string(&mut body, &[0u8; 32]);
304 writer::write_octet_string(&mut body, b"");
305 let der = wrap_sequence(&body);
306 assert!(
307 decode(&der).is_none(),
308 "x = p is not a field element and must be rejected"
309 );
310
311 let max_bytes = [0xffu8; 32];
313 let mut body = Vec::new();
314 writer::write_integer(&mut body, &max_bytes);
315 writer::write_integer(&mut body, &[0x01]);
316 writer::write_octet_string(&mut body, &[0u8; 32]);
317 writer::write_octet_string(&mut body, b"");
318 let der = wrap_sequence(&body);
319 assert!(decode(&der).is_none(), "x = 2^256 - 1 must be rejected");
320 }
321
322 #[test]
325 fn round_trip_x_p_minus_one() {
326 let p_minus_one = Fp::MODULUS.as_ref().wrapping_sub(&U256::ONE);
327 let mut ct = make_ct(b"q".to_vec());
328 ct.x = p_minus_one;
329 let der = encode(&ct);
330 let decoded = decode(&der).expect("decode round-trip with x = p - 1");
331 assert_eq!(decoded.x, p_minus_one);
332 }
333
334 #[test]
337 fn round_trip_65536_byte_ciphertext_uses_3byte_length() {
338 let payload = alloc::vec![0xa5u8; 65_536];
339 let ct = make_ct(payload.clone());
340 let der = encode(&ct);
341 let decoded = decode(&der).expect("decode 65,536-byte round-trip");
342 assert_eq!(decoded.ciphertext, payload);
343 }
344}