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: [u8; 32],
60 pub y: [u8; 32],
62 pub hash: [u8; HASH_LEN],
64 pub ciphertext: Vec<u8>,
66}
67
68#[must_use]
70pub fn encode(ct: &Sm2Ciphertext) -> Vec<u8> {
71 let mut body = Vec::with_capacity(ct.ciphertext.len() + 80);
72 writer::write_integer(&mut body, &ct.x);
73 writer::write_integer(&mut body, &ct.y);
74 writer::write_octet_string(&mut body, &ct.hash);
75 writer::write_octet_string(&mut body, &ct.ciphertext);
76 let mut out = Vec::with_capacity(body.len() + 4);
77 writer::write_sequence(&mut out, &body);
78 out
79}
80
81#[must_use]
85pub fn decode(input: &[u8]) -> Option<Sm2Ciphertext> {
86 let (body, rest) = reader::read_sequence(input)?;
87 if !rest.is_empty() {
88 return None;
89 }
90 let (x, body) = read_field_element(body)?;
91 let (y, body) = read_field_element(body)?;
92 let (hash_bytes, body) = reader::read_octet_string(body)?;
93 let (ciphertext, body) = reader::read_octet_string(body)?;
94 if !body.is_empty() {
95 return None;
96 }
97 if hash_bytes.len() != HASH_LEN {
98 return None;
99 }
100 let mut hash = [0u8; HASH_LEN];
101 hash.copy_from_slice(hash_bytes);
102 Some(Sm2Ciphertext {
103 x,
104 y,
105 hash,
106 ciphertext: ciphertext.to_vec(),
107 })
108}
109
110fn read_field_element(input: &[u8]) -> Option<([u8; 32], &[u8])> {
115 let (bytes, rest) = reader::read_integer(input)?;
116 if bytes.len() > 32 {
117 return None;
118 }
119 let mut padded = [0u8; 32];
120 padded[32 - bytes.len()..].copy_from_slice(bytes);
121 let value = U256::from_be_slice(&padded);
127 let in_field: bool = value.ct_lt(Fp::MODULUS.as_ref()).into();
128 if !in_field {
129 return None;
130 }
131 Some((padded, rest))
132}
133
134#[cfg(test)]
135mod tests {
136 use super::*;
137
138 fn make_ct(ciphertext: Vec<u8>) -> Sm2Ciphertext {
139 Sm2Ciphertext {
140 x: crate::u256_to_be32(&U256::from_be_hex(
141 "1234567890ABCDEF1234567890ABCDEF1234567890ABCDEF1234567890ABCDEF",
142 )),
143 y: crate::u256_to_be32(&U256::from_be_hex(
144 "FEDCBA0987654321FEDCBA0987654321FEDCBA0987654321FEDCBA0987654321",
145 )),
146 hash: [0xa5u8; 32],
147 ciphertext,
148 }
149 }
150
151 fn wrap_sequence(body: &[u8]) -> Vec<u8> {
155 let mut out = Vec::new();
156 writer::write_sequence(&mut out, body);
157 out
158 }
159
160 #[test]
162 fn round_trip_short() {
163 let ct = make_ct(b"hello world".to_vec());
164 let der = encode(&ct);
165 let decoded = decode(&der).expect("decode round-trip");
166 assert_eq!(decoded.x, ct.x);
167 assert_eq!(decoded.y, ct.y);
168 assert_eq!(decoded.hash, ct.hash);
169 assert_eq!(decoded.ciphertext, ct.ciphertext);
170 }
171
172 #[test]
175 fn round_trip_x_high_bit_set() {
176 let mut ct = make_ct(b"x".to_vec());
177 ct.x = crate::u256_to_be32(&U256::from_be_hex(
178 "FFEDCBA9876543210FEDCBA9876543210FEDCBA9876543210FEDCBA987654321",
179 ));
180 let der = encode(&ct);
181 let decoded = decode(&der).expect("decode high-bit round-trip");
182 assert_eq!(decoded.x, ct.x);
183 }
184
185 #[test]
188 fn round_trip_medium_ciphertext_300_bytes() {
189 let mut payload = alloc::vec![0u8; 300];
190 for (i, b) in payload.iter_mut().enumerate() {
191 #[allow(clippy::cast_possible_truncation)]
192 {
193 *b = (i as u8).wrapping_mul(13);
194 }
195 }
196 let ct = make_ct(payload.clone());
197 let der = encode(&ct);
198 let decoded = decode(&der).expect("decode 300-byte round-trip");
199 assert_eq!(decoded.ciphertext, payload);
200 }
201
202 #[test]
205 fn round_trip_empty_ciphertext() {
206 let ct = make_ct(Vec::new());
207 let der = encode(&ct);
208 let decoded = decode(&der).expect("decode empty-ciphertext round-trip");
209 assert!(decoded.ciphertext.is_empty());
210 }
211
212 #[test]
214 fn rejects_malformed() {
215 assert!(decode(&[]).is_none(), "empty input");
216 assert!(decode(&[0x30]).is_none(), "truncated SEQUENCE header");
217 assert!(decode(&[0x31, 0x00]).is_none(), "wrong outer tag");
218 assert!(decode(&[0x30, 0x05, 0x02, 0x01, 0x01]).is_none());
220 }
221
222 #[test]
226 fn rejects_wrong_hash_length() {
227 let bad_hash = [0x55u8; 31];
229 let ciphertext = b"x";
230 let mut body = Vec::new();
231 writer::write_integer(&mut body, &[0x01]);
232 writer::write_integer(&mut body, &[0x02]);
233 writer::write_octet_string(&mut body, &bad_hash);
234 writer::write_octet_string(&mut body, ciphertext);
235 let der = wrap_sequence(&body);
236 assert!(
237 decode(&der).is_none(),
238 "31-byte HASH must be rejected; SM3 always produces 32 bytes"
239 );
240 }
241
242 #[test]
247 fn rejects_non_canonical_x_leading_zero() {
248 let mut body = Vec::new();
250 body.extend_from_slice(&[0x02, 0x02, 0x00, 0x01]); writer::write_integer(&mut body, &[0x02]); writer::write_octet_string(&mut body, &[0u8; 32]);
253 writer::write_octet_string(&mut body, b"");
254 let der = wrap_sequence(&body);
255 assert!(
256 decode(&der).is_none(),
257 "non-canonical 00-pad on x must be rejected"
258 );
259 }
260
261 #[test]
264 fn rejects_negative_y_encoding() {
265 let mut body = Vec::new();
266 writer::write_integer(&mut body, &[0x01]);
267 body.extend_from_slice(&[0x02, 0x01, 0x80]); writer::write_octet_string(&mut body, &[0u8; 32]);
269 writer::write_octet_string(&mut body, b"");
270 let der = wrap_sequence(&body);
271 assert!(decode(&der).is_none());
272 }
273
274 #[test]
277 fn rejects_trailing_bytes() {
278 let ct = make_ct(b"hi".to_vec());
279 let mut der = encode(&ct);
280 der.push(0xff); assert!(decode(&der).is_none());
282 }
283
284 #[test]
288 fn round_trip_x_zero() {
289 let mut ct = make_ct(b"z".to_vec());
290 ct.x = [0u8; 32];
291 let der = encode(&ct);
292 let decoded = decode(&der).expect("decode round-trip with x = 0");
293 assert_eq!(decoded.x, [0u8; 32]);
294 assert_eq!(decoded.y, ct.y);
295 }
296
297 #[test]
303 fn rejects_x_at_or_above_p() {
304 let p = *Fp::MODULUS.as_ref();
306 let p_bytes = p.to_be_bytes();
307 let mut body = Vec::new();
308 writer::write_integer(&mut body, &p_bytes);
309 writer::write_integer(&mut body, &[0x01]);
310 writer::write_octet_string(&mut body, &[0u8; 32]);
311 writer::write_octet_string(&mut body, b"");
312 let der = wrap_sequence(&body);
313 assert!(
314 decode(&der).is_none(),
315 "x = p is not a field element and must be rejected"
316 );
317
318 let max_bytes = [0xffu8; 32];
320 let mut body = Vec::new();
321 writer::write_integer(&mut body, &max_bytes);
322 writer::write_integer(&mut body, &[0x01]);
323 writer::write_octet_string(&mut body, &[0u8; 32]);
324 writer::write_octet_string(&mut body, b"");
325 let der = wrap_sequence(&body);
326 assert!(decode(&der).is_none(), "x = 2^256 - 1 must be rejected");
327 }
328
329 #[test]
332 fn round_trip_x_p_minus_one() {
333 let p_minus_one = Fp::MODULUS.as_ref().wrapping_sub(&U256::ONE);
334 let mut ct = make_ct(b"q".to_vec());
335 ct.x = crate::u256_to_be32(&p_minus_one);
336 let der = encode(&ct);
337 let decoded = decode(&der).expect("decode round-trip with x = p - 1");
338 assert_eq!(decoded.x, crate::u256_to_be32(&p_minus_one));
339 }
340
341 #[test]
344 fn round_trip_65536_byte_ciphertext_uses_3byte_length() {
345 let payload = alloc::vec![0xa5u8; 65_536];
346 let ct = make_ct(payload.clone());
347 let der = encode(&ct);
348 let decoded = decode(&der).expect("decode 65,536-byte round-trip");
349 assert_eq!(decoded.ciphertext, payload);
350 }
351}