Skip to main content

gmssl_rs/sm2/
mod.rs

1// SM2 public key cryptography.
2//
3// SM2 is the Chinese national elliptic curve public key algorithm standard
4// (GM/T 0003-2012), covering digital signatures, public key encryption,
5// and key exchange on the sm2p256v1 curve.
6
7use std::mem::MaybeUninit;
8use gmssl_rs_sys;
9
10use crate::error::{ok_or_library_error, verify_result, GmsslError};
11use crate::pem_helpers;
12
13/// SM2 key pair (public + optional private key).
14#[derive(Debug)]
15pub struct Sm2Key {
16    key: gmssl_rs_sys::SM2_KEY,
17    has_private_key: bool,
18}
19
20impl Sm2Key {
21    /// Generate a new SM2 key pair.
22    pub fn generate() -> Result<Self, GmsslError> {
23        let mut key = MaybeUninit::uninit();
24        ok_or_library_error(
25            unsafe { gmssl_rs_sys::sm2_key_generate(key.as_mut_ptr()) },
26            "sm2_key_generate",
27        )?;
28        Ok(Sm2Key {
29            key: unsafe { key.assume_init() },
30            has_private_key: true,
31        })
32    }
33
34    /// Import from PKCS#8 encrypted private key PEM (in-memory).
35    pub fn from_encrypted_private_key_pem(
36        pem_data: &[u8],
37        password: &str,
38    ) -> Result<Self, GmsslError> {
39        let pass_c = std::ffi::CString::new(password)
40            .map_err(|_| GmsslError::InvalidInput("password contains NUL byte"))?;
41
42        let mut key = MaybeUninit::uninit();
43        pem_helpers::read_pem_data(pem_data, |fp| unsafe {
44            gmssl_rs_sys::sm2_private_key_info_decrypt_from_pem(
45                key.as_mut_ptr(),
46                pass_c.as_ptr(),
47                fp,
48            )
49        })?;
50        // Re-read because read_pem_data closes the fp first; we need the return code
51        let fp = unsafe { pem_helpers::file_from_bytes(pem_data)? };
52        let ret = unsafe {
53            gmssl_rs_sys::sm2_private_key_info_decrypt_from_pem(
54                key.as_mut_ptr(),
55                pass_c.as_ptr(),
56                fp,
57            )
58        };
59        unsafe { libc::fclose(fp) };
60        ok_or_library_error(ret, "sm2_private_key_info_decrypt_from_pem")?;
61        Ok(Sm2Key {
62            key: unsafe { key.assume_init() },
63            has_private_key: true,
64        })
65    }
66
67    /// Export to PKCS#8 encrypted private key PEM (in-memory).
68    pub fn to_encrypted_private_key_pem(
69        &self,
70        password: &str,
71    ) -> Result<Vec<u8>, GmsslError> {
72        if !self.has_private_key {
73            return Err(GmsslError::InvalidKey("no private key to export"));
74        }
75        let pass_c = std::ffi::CString::new(password)
76            .map_err(|_| GmsslError::InvalidInput("password contains NUL byte"))?;
77
78        unsafe {
79            pem_helpers::collect_to_bytes(|fp| {
80                gmssl_rs_sys::sm2_private_key_info_encrypt_to_pem(
81                    &self.key,
82                    pass_c.as_ptr(),
83                    fp,
84                )
85            })
86        }
87    }
88
89    /// Import from public key PEM (in-memory).
90    pub fn from_public_key_pem(pem_data: &[u8]) -> Result<Self, GmsslError> {
91        let mut key = MaybeUninit::uninit();
92        let fp = unsafe { pem_helpers::file_from_bytes(pem_data)? };
93        let ret = unsafe {
94            gmssl_rs_sys::sm2_public_key_info_from_pem(key.as_mut_ptr(), fp)
95        };
96        unsafe { libc::fclose(fp) };
97        ok_or_library_error(ret, "sm2_public_key_info_from_pem")?;
98        Ok(Sm2Key {
99            key: unsafe { key.assume_init() },
100            has_private_key: false,
101        })
102    }
103
104    /// Export to public key PEM (in-memory).
105    pub fn to_public_key_pem(&self) -> Result<Vec<u8>, GmsslError> {
106        unsafe {
107            pem_helpers::collect_to_bytes(|fp| {
108                gmssl_rs_sys::sm2_public_key_info_to_pem(&self.key, fp)
109            })
110        }
111    }
112
113    /// Import from private key PEM (PKCS#8 PrivateKeyInfo, in-memory).
114    pub fn from_private_key_pem(pem_data: &[u8]) -> Result<Self, GmsslError> {
115        let mut key = MaybeUninit::uninit();
116        let fp = unsafe { pem_helpers::file_from_bytes(pem_data)? };
117        let ret = unsafe {
118            gmssl_rs_sys::sm2_private_key_info_from_pem(key.as_mut_ptr(), fp)
119        };
120        unsafe { libc::fclose(fp) };
121        ok_or_library_error(ret, "sm2_private_key_info_from_pem")?;
122        Ok(Sm2Key {
123            key: unsafe { key.assume_init() },
124            has_private_key: true,
125        })
126    }
127
128    /// Export to private key PEM (PKCS#8 PrivateKeyInfo, in-memory).
129    pub fn to_private_key_pem(&self) -> Result<Vec<u8>, GmsslError> {
130        if !self.has_private_key {
131            return Err(GmsslError::InvalidKey("no private key to export"));
132        }
133        unsafe {
134            pem_helpers::collect_to_bytes(|fp| {
135                gmssl_rs_sys::sm2_private_key_info_to_pem(&self.key, fp)
136            })
137        }
138    }
139
140    /// Import from public key DER (SubjectPublicKeyInfo).
141    pub fn from_public_key_der(der: &[u8]) -> Result<Self, GmsslError> {
142        unsafe {
143            pem_helpers::parse_der(der, |key, pin, pinlen| {
144                gmssl_rs_sys::sm2_public_key_info_from_der(key, pin, pinlen)
145            })
146        }
147        .map(|key| Sm2Key {
148            key,
149            has_private_key: false,
150        })
151    }
152
153    /// Export to public key DER (SubjectPublicKeyInfo).
154    pub fn to_public_key_der(&self) -> Result<Vec<u8>, GmsslError> {
155        unsafe {
156            pem_helpers::collect_der(512, |out, outlen| {
157                gmssl_rs_sys::sm2_public_key_info_to_der(&self.key, out, outlen)
158            })
159        }
160    }
161
162    /// Import from private key DER (PKCS#8 PrivateKeyInfo).
163    pub fn from_private_key_der(der: &[u8]) -> Result<Self, GmsslError> {
164        unsafe {
165            let mut attrs: *const u8 = std::ptr::null();
166            let mut attrslen: usize = 0;
167            pem_helpers::parse_der(der, |key, pin, pinlen| {
168                gmssl_rs_sys::sm2_private_key_info_from_der(
169                    key,
170                    &mut attrs,
171                    &mut attrslen,
172                    pin,
173                    pinlen,
174                )
175            })
176        }
177        .map(|key| Sm2Key {
178            key,
179            has_private_key: true,
180        })
181    }
182
183    /// Export to private key DER (PKCS#8 PrivateKeyInfo).
184    pub fn to_private_key_der(&self) -> Result<Vec<u8>, GmsslError> {
185        if !self.has_private_key {
186            return Err(GmsslError::InvalidKey("no private key to export"));
187        }
188        unsafe {
189            pem_helpers::collect_der(512, |out, outlen| {
190                gmssl_rs_sys::sm2_private_key_info_to_der(&self.key, out, outlen)
191            })
192        }
193    }
194
195    /// Import from public key PEM file.
196    pub fn from_public_key_pem_file(path: &str) -> Result<Self, GmsslError> {
197        let mut key = MaybeUninit::uninit();
198        let fp = unsafe { pem_helpers::file_open_read(path)? };
199        let ret = unsafe {
200            gmssl_rs_sys::sm2_public_key_info_from_pem(key.as_mut_ptr(), fp)
201        };
202        unsafe { libc::fclose(fp) };
203        ok_or_library_error(ret, "sm2_public_key_info_from_pem")?;
204        Ok(Sm2Key {
205            key: unsafe { key.assume_init() },
206            has_private_key: false,
207        })
208    }
209
210    /// Export to public key PEM file.
211    pub fn to_public_key_pem_file(&self, path: &str) -> Result<(), GmsslError> {
212        let fp = unsafe { pem_helpers::file_open_write(path)? };
213        let ret = unsafe {
214            gmssl_rs_sys::sm2_public_key_info_to_pem(&self.key, fp)
215        };
216        unsafe { libc::fclose(fp) };
217        ok_or_library_error(ret, "sm2_public_key_info_to_pem")
218    }
219
220    /// Import from encrypted private key PEM file.
221    pub fn from_encrypted_private_key_pem_file(
222        path: &str,
223        password: &str,
224    ) -> Result<Self, GmsslError> {
225        let pass_c = std::ffi::CString::new(password)
226            .map_err(|_| GmsslError::InvalidInput("password contains NUL byte"))?;
227        let mut key = MaybeUninit::uninit();
228        let fp = unsafe { pem_helpers::file_open_read(path)? };
229        let ret = unsafe {
230            gmssl_rs_sys::sm2_private_key_info_decrypt_from_pem(
231                key.as_mut_ptr(),
232                pass_c.as_ptr(),
233                fp,
234            )
235        };
236        unsafe { libc::fclose(fp) };
237        ok_or_library_error(ret, "sm2_private_key_info_decrypt_from_pem")?;
238        Ok(Sm2Key {
239            key: unsafe { key.assume_init() },
240            has_private_key: true,
241        })
242    }
243
244    /// Export to encrypted private key PEM file.
245    pub fn to_encrypted_private_key_pem_file(
246        &self,
247        path: &str,
248        password: &str,
249    ) -> Result<(), GmsslError> {
250        if !self.has_private_key {
251            return Err(GmsslError::InvalidKey("no private key to export"));
252        }
253        let pass_c = std::ffi::CString::new(password)
254            .map_err(|_| GmsslError::InvalidInput("password contains NUL byte"))?;
255        let fp = unsafe { pem_helpers::file_open_write(path)? };
256        let ret = unsafe {
257            gmssl_rs_sys::sm2_private_key_info_encrypt_to_pem(
258                &self.key,
259                pass_c.as_ptr(),
260                fp,
261            )
262        };
263        unsafe { libc::fclose(fp) };
264        ok_or_library_error(ret, "sm2_private_key_info_encrypt_to_pem")
265    }
266
267    /// Compute the Z value (hash of signer identity + curve parameters + public key).
268    pub fn compute_z(&self, id: &str) -> Result<[u8; 32], GmsslError> {
269        let id_c = std::ffi::CString::new(id)
270            .map_err(|_| GmsslError::InvalidInput("ID contains NUL byte"))?;
271        let mut z = [0u8; 32];
272        ok_or_library_error(
273            unsafe {
274                gmssl_rs_sys::sm2_compute_z(
275                    z.as_mut_ptr(),
276                    &self.key.public_key,
277                    id_c.as_ptr(),
278                    id.len(),
279                )
280            },
281            "sm2_compute_z",
282        )?;
283        Ok(z)
284    }
285
286    /// Returns true if this key has a private key.
287    pub fn has_private_key(&self) -> bool {
288        self.has_private_key
289    }
290
291    // Internal: get raw key pointer
292    pub(crate) fn as_ptr(&self) -> *const gmssl_rs_sys::SM2_KEY {
293        &self.key
294    }
295}
296
297/// SM2 streaming signer.
298///
299/// Follows the init/update/finish pattern for signing arbitrary-length messages.
300/// The signer computes SM3(Z || message) internally, where Z is derived from
301/// the signer's identity and public key per GM/T 0003.5.
302pub struct Sm2Signer {
303    ctx: Box<MaybeUninit<gmssl_rs_sys::SM2_SIGN_CTX>>,
304}
305
306impl Sm2Signer {
307    /// Create a new SM2 signer.
308    ///
309    /// `id` is the signer's identity string. If `None`, the default ID
310    /// "1234567812345678" is used per the GM/T standard.
311    pub fn new(key: &Sm2Key, id: Option<&str>) -> Result<Self, GmsslError> {
312        if !key.has_private_key {
313            return Err(GmsslError::InvalidKey("private key required for signing"));
314        }
315        let id = id.unwrap_or("1234567812345678");
316        let id_c = std::ffi::CString::new(id)
317            .map_err(|_| GmsslError::InvalidInput("ID contains NUL byte"))?;
318
319        let mut ctx = Box::new(MaybeUninit::uninit());
320        ok_or_library_error(
321            unsafe {
322                gmssl_rs_sys::sm2_sign_init(
323                    ctx.as_mut_ptr(),
324                    key.as_ptr(),
325                    id_c.as_ptr(),
326                    id.len(),
327                )
328            },
329            "sm2_sign_init",
330        )?;
331        Ok(Sm2Signer { ctx })
332    }
333
334    /// Feed message data into the signature computation.
335    pub fn update(&mut self, data: &[u8]) -> Result<(), GmsslError> {
336        ok_or_library_error(
337            unsafe {
338                gmssl_rs_sys::sm2_sign_update(self.ctx.as_mut_ptr(), data.as_ptr(), data.len())
339            },
340            "sm2_sign_update",
341        )
342    }
343
344    /// Finalize and produce the DER-encoded signature.
345    pub fn finish(&mut self) -> Result<Vec<u8>, GmsslError> {
346        let mut sig = vec![0u8; gmssl_rs_sys::SM2_MAX_SIGNATURE_SIZE];
347        let mut siglen: usize = sig.len();
348        ok_or_library_error(
349            unsafe {
350                gmssl_rs_sys::sm2_sign_finish(
351                    self.ctx.as_mut_ptr(),
352                    sig.as_mut_ptr(),
353                    &mut siglen,
354                )
355            },
356            "sm2_sign_finish",
357        )?;
358        // GmSSL sets *siglen = 0 before DER encoding, causing size_t wrap.
359        // Compute actual DER size from ASN.1 SEQUENCE header: 0x30 || len || content
360        truncate_der_sequence(&mut sig);
361        Ok(sig)
362    }
363
364    /// One-shot sign: sign a complete message.
365    pub fn sign(key: &Sm2Key, id: Option<&str>, data: &[u8]) -> Result<Vec<u8>, GmsslError> {
366        let mut signer = Sm2Signer::new(key, id)?;
367        signer.update(data)?;
368        signer.finish()
369    }
370}
371
372impl std::fmt::Debug for Sm2Signer {
373    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
374        f.debug_struct("Sm2Signer").finish()
375    }
376}
377
378/// SM2 streaming verifier.
379pub struct Sm2Verifier {
380    ctx: Box<MaybeUninit<gmssl_rs_sys::SM2_VERIFY_CTX>>,
381}
382
383impl Sm2Verifier {
384    /// Create a new SM2 verifier.
385    pub fn new(key: &Sm2Key, id: Option<&str>) -> Result<Self, GmsslError> {
386        let id = id.unwrap_or("1234567812345678");
387        let id_c = std::ffi::CString::new(id)
388            .map_err(|_| GmsslError::InvalidInput("ID contains NUL byte"))?;
389
390        let mut ctx = Box::new(MaybeUninit::uninit());
391        ok_or_library_error(
392            unsafe {
393                gmssl_rs_sys::sm2_verify_init(
394                    ctx.as_mut_ptr(),
395                    key.as_ptr(),
396                    id_c.as_ptr(),
397                    id.len(),
398                )
399            },
400            "sm2_verify_init",
401        )?;
402        Ok(Sm2Verifier { ctx })
403    }
404
405    /// Feed message data into the verification computation.
406    pub fn update(&mut self, data: &[u8]) -> Result<(), GmsslError> {
407        ok_or_library_error(
408            unsafe {
409                gmssl_rs_sys::sm2_verify_update(self.ctx.as_mut_ptr(), data.as_ptr(), data.len())
410            },
411            "sm2_verify_update",
412        )
413    }
414
415    /// Finalize and verify the signature against the accumulated message.
416    ///
417    /// Returns `Ok(true)` if valid, `Ok(false)` if invalid, `Err` on library error.
418    pub fn finish(&mut self, sig: &[u8]) -> Result<bool, GmsslError> {
419        verify_result(
420            unsafe {
421                gmssl_rs_sys::sm2_verify_finish(
422                    self.ctx.as_mut_ptr(),
423                    sig.as_ptr(),
424                    sig.len(),
425                )
426            },
427            "sm2_verify_finish",
428        )
429    }
430
431    /// One-shot verify: verify a signature over a complete message.
432    pub fn verify(
433        key: &Sm2Key,
434        id: Option<&str>,
435        data: &[u8],
436        sig: &[u8],
437    ) -> Result<bool, GmsslError> {
438        let mut verifier = Sm2Verifier::new(key, id)?;
439        verifier.update(data)?;
440        verifier.finish(sig)
441    }
442}
443
444impl std::fmt::Debug for Sm2Verifier {
445    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
446        f.debug_struct("Sm2Verifier").finish()
447    }
448}
449
450/// SM2 encryption (one-shot).
451///
452/// Encrypts data for the given public key. Maximum plaintext size is 255 bytes
453/// (SM2 ciphertext format limitation).
454pub fn sm2_encrypt(key: &Sm2Key, data: &[u8]) -> Result<Vec<u8>, GmsslError> {
455    if data.len() > gmssl_rs_sys::SM2_MAX_PLAINTEXT_SIZE {
456        return Err(GmsslError::InvalidInput(
457            "SM2 plaintext exceeds 255 bytes maximum",
458        ));
459    }
460    let mut out = vec![0u8; gmssl_rs_sys::SM2_MAX_CIPHERTEXT_SIZE];
461    let mut outlen: usize = out.len();
462    ok_or_library_error(
463        unsafe {
464            gmssl_rs_sys::sm2_encrypt(
465                key.as_ptr(),
466                data.as_ptr(),
467                data.len(),
468                out.as_mut_ptr(),
469                &mut outlen,
470            )
471        },
472        "sm2_encrypt",
473    )?;
474    // GmSSL sets *outlen = 0 before DER encoding, causing size_t wrap.
475    truncate_der_sequence(&mut out);
476    Ok(out)
477}
478
479/// SM2 decryption (one-shot).
480///
481/// Decrypts data using the given private key.
482pub fn sm2_decrypt(key: &Sm2Key, ciphertext: &[u8]) -> Result<Vec<u8>, GmsslError> {
483    if !key.has_private_key {
484        return Err(GmsslError::InvalidKey("private key required for decryption"));
485    }
486    let mut out = vec![0u8; ciphertext.len()];
487    let mut outlen: usize = out.len();
488    ok_or_library_error(
489        unsafe {
490            gmssl_rs_sys::sm2_decrypt(
491                key.as_ptr(),
492                ciphertext.as_ptr(),
493                ciphertext.len(),
494                out.as_mut_ptr(),
495                &mut outlen,
496            )
497        },
498        "sm2_decrypt",
499    )?;
500    out.truncate(outlen);
501    Ok(out)
502}
503
504/// SM2 ECDH key exchange.
505///
506/// Computes the shared secret from the local private key and the peer's public key.
507pub fn sm2_ecdh(key: &Sm2Key, peer_key: &Sm2Key) -> Result<[u8; 32], GmsslError> {
508    if !key.has_private_key {
509        return Err(GmsslError::InvalidKey("private key required for ECDH"));
510    }
511    let mut out = [0u8; 32];
512    ok_or_library_error(
513        unsafe { gmssl_rs_sys::sm2_do_ecdh(key.as_ptr(), peer_key.as_ptr(), out.as_mut_ptr()) },
514        "sm2_do_ecdh",
515    )?;
516    Ok(out)
517}
518
519/// Truncate a Vec containing a DER SEQUENCE to its actual encoded size.
520///
521/// GmSSL DER encoding functions set `*outlen = 0` before starting, causing
522/// unsigned size_t wrap when they decrement. We recover the actual size from
523/// the ASN.1 SEQUENCE header: tag (0x30) + length byte + content.
524fn truncate_der_sequence(data: &mut Vec<u8>) {
525    if data.len() >= 2 && data[0] == 0x30 {
526        let content_len = data[1] as usize;
527        let total = if content_len < 0x80 {
528            // Short form: length byte IS the content length
529            2 + content_len
530        } else if content_len == 0x81 && data.len() >= 3 {
531            // Long form: 1 byte of length follows
532            2 + 1 + data[2] as usize
533        } else if content_len == 0x82 && data.len() >= 4 {
534            // Long form: 2 bytes of length follow
535            let l = u16::from_be_bytes([data[2], data[3]]) as usize;
536            2 + 2 + l
537        } else {
538            return; // unknown encoding, don't truncate
539        };
540        if total <= data.len() {
541            data.truncate(total);
542        }
543    }
544}
545
546#[cfg(test)]
547mod tests;