synta-certificate 0.2.6

X.509 certificate structures for synta ASN.1 library
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
//! NSS-backed [`crate::crypto::BackendPrivateKey`] methods.
//!
//! Provides the full set of backend-agnostic key operations for the NSS
//! backend: PEM helpers, PKCS#8 import/export, public-key extraction, RSA
//! decryption, and key-pair generation for RSA, EC, and EdDSA.

use std::ptr;

use nss_sys::nspr::{PR_FALSE, PR_TRUE};
use nss_sys::{SECItemStr, SECItemType, SECStatus};

use super::ensure_nss_init;
use super::ffi::{
    CKMechanismType, PK11RsaGenParams, PK11_ExportDERPrivateKeyInfo, PK11_FreeSlot,
    PK11_GenerateKeyPair, PK11_GetInternalSlot, PK11_ImportDERPrivateKeyInfoAndReturnKey,
    SECITEM_FreeItem, SECKEYPrivateKeyStr, SECKEYPublicKeyStr, SECKEY_ConvertToPublicKey,
    SECKEY_DestroyPrivateKey, SECKEY_DestroyPublicKey, SECKEY_EncodeDERSubjectPublicKeyInfo,
    SECKEY_PublicKeyStrength, CKM_EC_EDWARDS_KEY_PAIR_GEN, CKM_EC_KEY_PAIR_GEN,
    CKM_ML_DSA_KEY_PAIR_GEN, CKM_RSA_PKCS_KEY_PAIR_GEN, CKP_ML_DSA_44, CKP_ML_DSA_65,
    CKP_ML_DSA_87, KU_DIGITAL_SIGNATURE,
};
use crate::crypto::PrivateKeyError;

// ── Local error helper ────────────────────────────────────────────────────────

#[derive(Debug)]
struct NssKeyError(String);

impl std::fmt::Display for NssKeyError {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        f.write_str(&self.0)
    }
}

impl std::error::Error for NssKeyError {}

fn key_err(msg: impl Into<String>) -> PrivateKeyError {
    PrivateKeyError::new(NssKeyError(msg.into()))
}

// ── NSS key-pair generation ───────────────────────────────────────────────────

/// Generate a key pair in the NSS internal slot using `mechanism` and `param`,
/// export the private key as PKCS#8 DER, and return a [`BackendPrivateKey`].
pub(super) fn nss_generate_key(
    mechanism: CKMechanismType,
    param: *mut std::ffi::c_void,
) -> Result<crate::crypto::BackendPrivateKey, PrivateKeyError> {
    if !ensure_nss_init() {
        return Err(key_err("NSS initialisation failed"));
    }

    // SAFETY: PK11_GetInternalSlot is thread-safe after NSS_NoDB_Init returns.
    let slot = unsafe { PK11_GetInternalSlot() };
    if slot.is_null() {
        return Err(key_err("PK11_GetInternalSlot failed"));
    }

    let mut pub_key: *mut SECKEYPublicKeyStr = ptr::null_mut();

    // SAFETY: slot is non-null; param is valid for the duration of this call;
    // pub_key receives the output public key pointer.
    let priv_key = unsafe {
        PK11_GenerateKeyPair(
            slot,
            mechanism,
            param,
            &mut pub_key,
            PR_FALSE,        // isPerm: session (in-memory) key
            PR_TRUE,         // isSensitive
            ptr::null_mut(), // wincx: no PIN
        )
    };

    // Release the slot reference — key holds its own.
    // SAFETY: slot is non-null.
    unsafe { PK11_FreeSlot(slot) };

    if priv_key.is_null() {
        return Err(key_err("PK11_GenerateKeyPair failed"));
    }

    // We do not need the public key handle; discard it immediately.
    if !pub_key.is_null() {
        // SAFETY: pub_key is non-null.
        unsafe { SECKEY_DestroyPublicKey(pub_key) };
    }

    // Export the private key as unencrypted PKCS#8 DER.
    // SAFETY: priv_key is non-null.
    let pkcs8_item = unsafe { PK11_ExportDERPrivateKeyInfo(priv_key, ptr::null_mut()) };

    // Free the private key handle before any possible error return below.
    // SAFETY: priv_key is non-null.
    unsafe { SECKEY_DestroyPrivateKey(priv_key) };

    if pkcs8_item.is_null() {
        return Err(key_err("PK11_ExportDERPrivateKeyInfo failed"));
    }

    // Copy the PKCS#8 DER bytes out of the NSS-allocated buffer.
    // SAFETY: pkcs8_item is non-null; data and len are valid after a successful
    // PK11_ExportDERPrivateKeyInfo call.
    let pkcs8_der =
        unsafe { std::slice::from_raw_parts((*pkcs8_item).data, (*pkcs8_item).len as usize) }
            .to_vec();

    // SAFETY: PR_TRUE frees both .data and the SECItem struct itself.
    unsafe { SECITEM_FreeItem(pkcs8_item, PR_TRUE) };

    let cell = std::sync::OnceLock::new();
    cell.set(pkcs8_der).expect("fresh OnceLock");
    Ok(crate::crypto::BackendPrivateKey {
        pkcs8_der: cell,
        spki_cache: None,
        pkcs11: None,
    })
}

// ── Key-from-components DER builders ─────────────────────────────────────────

/// If `bytes` has its high bit set (i.e. would be interpreted as negative in
/// two's-complement), prepend a zero byte so the INTEGER is positive.
fn ensure_positive(bytes: &[u8]) -> Vec<u8> {
    if bytes.first().is_some_and(|&b| b & 0x80 != 0) {
        let mut v = Vec::with_capacity(bytes.len() + 1);
        v.push(0u8);
        v.extend_from_slice(bytes);
        v
    } else {
        bytes.to_vec()
    }
}

/// Build a PKCS#8 `OneAsymmetricKey` DER wrapping an already-DER-encoded inner
/// private key.  `alg` is the `privateKeyAlgorithm` field.
fn build_pkcs8_der(
    alg: crate::AlgorithmIdentifier<'_>,
    inner_key_der: &[u8],
) -> synta::Result<Vec<u8>> {
    use synta::types::string::OctetStringRef;
    let pki = crate::pkcs8_types::OneAsymmetricKey {
        version: synta::Integer::from_i64(0),
        private_key_algorithm: alg,
        private_key: OctetStringRef::new(inner_key_der),
        attributes: None,
        public_key: None,
    };
    pki.to_der()
}

/// Build a DER-encoded `ECPrivateKey` (RFC 5915) and wrap it in PKCS#8.
///
/// `curve_oid_components` is the arc array for the named-curve OID (e.g.
/// [`crate::oids::EC_CURVE_P256`]).
fn nss_ec_pkcs8_from_components(
    d: &[u8],
    x: &[u8],
    y: &[u8],
    curve_oid_components: &[u32],
) -> Result<Vec<u8>, String> {
    use synta::tag::{Tag, TAG_SEQUENCE};
    use synta::types::string::BitStringRef;
    use synta::{Encoder, Encoding, Integer, ObjectIdentifier};

    // Inner ECPrivateKey ::= SEQUENCE { version INTEGER(1), privateKey OCTET STRING,
    //   publicKey [1] BIT STRING OPTIONAL }
    let point = {
        let mut v = Vec::with_capacity(1 + x.len() + y.len());
        v.push(0x04); // uncompressed point prefix
        v.extend_from_slice(x);
        v.extend_from_slice(y);
        v
    };
    let inner_der = (|| -> synta::Result<Vec<u8>> {
        let mut enc = Encoder::new(Encoding::Der);
        enc.start_constructed_no_guard(Tag::universal_constructed(TAG_SEQUENCE))?;
        enc.encode(&Integer::from_i64(1))?;
        // privateKey OCTET STRING wrapping the raw scalar
        let d_octet = synta::types::string::OctetStringRef::new(d);
        enc.encode(&d_octet)?;
        // [1] EXPLICIT BIT STRING for the public key point
        enc.start_constructed_no_guard(Tag::context_specific_constructed(1))?;
        let pub_bit = BitStringRef::new(&point, 0)?;
        enc.encode(&pub_bit)?;
        enc.end_constructed()?;
        enc.end_constructed()?;
        enc.finish()
    })()
    .map_err(|e| format!("ECPrivateKey DER encoding failed: {e}"))?;

    // Outer AlgorithmIdentifier: id-ecPublicKey + curve OID as parameters
    let curve_oid = ObjectIdentifier::new(curve_oid_components)
        .map_err(|e| format!("invalid curve OID components: {e}"))?;
    let ec_pk_oid = ObjectIdentifier::new(crate::oids::EC_PUBLIC_KEY)
        .map_err(|e| format!("invalid EC_PUBLIC_KEY OID: {e}"))?;
    let alg = crate::AlgorithmIdentifier {
        algorithm: ec_pk_oid,
        parameters: Some(synta::Element::ObjectIdentifier(curve_oid)),
    };

    build_pkcs8_der(alg, &inner_der).map_err(|e| format!("PKCS#8 DER encoding failed: {e}"))
}

/// Build a DER-encoded `RSAPrivateKey` (RFC 3447) and wrap it in PKCS#8.
fn nss_rsa_pkcs8_from_components(
    c: &crate::crypto::RsaPrivateComponents<'_>,
) -> Result<Vec<u8>, String> {
    use synta::tag::{Tag, TAG_SEQUENCE};
    use synta::{Encoder, Encoding, Integer, ObjectIdentifier};

    // Encode each unsigned big-endian component as a positive DER INTEGER.
    let n = ensure_positive(c.n);
    let e = ensure_positive(c.e);
    let d = ensure_positive(c.d);
    let p = ensure_positive(c.p);
    let q = ensure_positive(c.q);
    let dp = ensure_positive(c.dp);
    let dq = ensure_positive(c.dq);
    let qi = ensure_positive(c.qi);

    // Inner RSAPrivateKey ::= SEQUENCE { version 0, n, e, d, p, q, dp, dq, qi }
    let inner_der = (|| -> synta::Result<Vec<u8>> {
        let mut enc = Encoder::new(Encoding::Der);
        enc.start_constructed_no_guard(Tag::universal_constructed(TAG_SEQUENCE))?;
        enc.encode(&Integer::from_i64(0))?;
        enc.encode(&Integer::from_bytes(&n))?;
        enc.encode(&Integer::from_bytes(&e))?;
        enc.encode(&Integer::from_bytes(&d))?;
        enc.encode(&Integer::from_bytes(&p))?;
        enc.encode(&Integer::from_bytes(&q))?;
        enc.encode(&Integer::from_bytes(&dp))?;
        enc.encode(&Integer::from_bytes(&dq))?;
        enc.encode(&Integer::from_bytes(&qi))?;
        enc.end_constructed()?;
        enc.finish()
    })()
    .map_err(|e| format!("RSAPrivateKey DER encoding failed: {e}"))?;

    // Outer AlgorithmIdentifier: rsaEncryption + NULL parameters
    let rsa_oid = ObjectIdentifier::new(crate::oids::RSA_ENCRYPTION)
        .map_err(|e| format!("invalid RSA_ENCRYPTION OID: {e}"))?;
    let alg = crate::AlgorithmIdentifier {
        algorithm: rsa_oid,
        parameters: Some(synta::Element::Null(synta::Null)),
    };

    build_pkcs8_der(alg, &inner_der).map_err(|e| format!("PKCS#8 DER encoding failed: {e}"))
}

// ── Private helpers ───────────────────────────────────────────────────────────

/// Import `pkcs8_der` into NSS, convert to a public key, and return the RSA
/// modulus size in bits.  Returns `None` on any failure.
fn nss_rsa_key_bit_size(pkcs8_der: &[u8]) -> Option<i64> {
    if !ensure_nss_init() {
        return None;
    }
    // SAFETY: PK11_GetInternalSlot is thread-safe after NSS_NoDB_Init returns.
    let slot = unsafe { PK11_GetInternalSlot() };
    if slot.is_null() {
        return None;
    }

    let pkcs8_item = SECItemStr {
        type_: SECItemType::siBuffer,
        data: pkcs8_der.as_ptr() as *mut _,
        len: pkcs8_der.len() as u32,
    };
    let mut priv_key: *mut SECKEYPrivateKeyStr = ptr::null_mut();

    // SAFETY: slot, pkcs8_item, and priv_key are valid for this call.
    let status = unsafe {
        PK11_ImportDERPrivateKeyInfoAndReturnKey(
            slot,
            &pkcs8_item,
            ptr::null(),
            ptr::null(),
            PR_FALSE,
            PR_TRUE,
            KU_DIGITAL_SIGNATURE,
            &mut priv_key,
            ptr::null_mut(),
        )
    };
    // SAFETY: slot is non-null.
    unsafe { PK11_FreeSlot(slot) };

    if status != SECStatus::SECSuccess || priv_key.is_null() {
        return None;
    }

    // SAFETY: priv_key is non-null.
    let pub_key = unsafe { SECKEY_ConvertToPublicKey(priv_key) };
    // SAFETY: priv_key is non-null.
    unsafe { SECKEY_DestroyPrivateKey(priv_key) };

    if pub_key.is_null() {
        return None;
    }

    // SAFETY: pub_key is non-null.
    let byte_len = unsafe { SECKEY_PublicKeyStrength(pub_key) };
    // SAFETY: pub_key is non-null.
    unsafe { SECKEY_DestroyPublicKey(pub_key) };

    Some((byte_len as i64) * 8)
}

/// Import `pkcs8_der` into NSS, derive the public key, and return its
/// DER-encoded SubjectPublicKeyInfo.
fn nss_public_key_spki_der(pkcs8_der: &[u8]) -> Result<Vec<u8>, PrivateKeyError> {
    if !ensure_nss_init() {
        return Err(key_err("NSS initialisation failed"));
    }

    // SAFETY: PK11_GetInternalSlot is thread-safe after NSS_NoDB_Init returns.
    let slot = unsafe { PK11_GetInternalSlot() };
    if slot.is_null() {
        return Err(key_err("PK11_GetInternalSlot failed"));
    }

    let pkcs8_item = SECItemStr {
        type_: SECItemType::siBuffer,
        data: pkcs8_der.as_ptr() as *mut _,
        len: pkcs8_der.len() as u32,
    };
    let mut priv_key: *mut SECKEYPrivateKeyStr = ptr::null_mut();

    // SAFETY: slot, pkcs8_item, and priv_key are valid for this call.
    let status = unsafe {
        PK11_ImportDERPrivateKeyInfoAndReturnKey(
            slot,
            &pkcs8_item,
            ptr::null(),
            ptr::null(),
            PR_FALSE,
            PR_TRUE,
            KU_DIGITAL_SIGNATURE,
            &mut priv_key,
            ptr::null_mut(),
        )
    };
    // SAFETY: slot is non-null.
    unsafe { PK11_FreeSlot(slot) };

    if status != SECStatus::SECSuccess || priv_key.is_null() {
        return Err(key_err(
            "PK11_ImportDERPrivateKeyInfoAndReturnKey failed: cannot import private key",
        ));
    }

    // SAFETY: priv_key is non-null.
    let pub_key = unsafe { SECKEY_ConvertToPublicKey(priv_key) };
    // SAFETY: priv_key is non-null.
    unsafe { SECKEY_DestroyPrivateKey(priv_key) };

    if pub_key.is_null() {
        return Err(key_err("SECKEY_ConvertToPublicKey failed"));
    }

    // SAFETY: pub_key is non-null.
    let spki_item = unsafe { SECKEY_EncodeDERSubjectPublicKeyInfo(pub_key) };
    // SAFETY: pub_key is non-null.
    unsafe { SECKEY_DestroyPublicKey(pub_key) };

    if spki_item.is_null() {
        return Err(key_err("SECKEY_EncodeDERSubjectPublicKeyInfo failed"));
    }

    // SAFETY: spki_item is non-null; data and len are valid after a successful call.
    let spki_der =
        unsafe { std::slice::from_raw_parts((*spki_item).data, (*spki_item).len as usize) }
            .to_vec();

    // SAFETY: PR_TRUE frees both .data and the SECItem struct itself.
    unsafe { SECITEM_FreeItem(spki_item, PR_TRUE) };

    Ok(spki_der)
}

/// Generate an ML-DSA key pair in the NSS internal slot.
///
/// `mldsa_variant` must be one of `"ML-DSA-44"`, `"ML-DSA-65"`, or `"ML-DSA-87"`.
/// Uses `CKM_ML_DSA_KEY_PAIR_GEN` with the corresponding `CKP_ML_DSA_44/65/87`
/// parameter set (PKCS#11 v3.2 §2.3.9).
pub(super) fn generate_ml_dsa(
    mldsa_variant: &str,
) -> Result<crate::crypto::BackendPrivateKey, PrivateKeyError> {
    let param_set: std::ffi::c_ulong = match mldsa_variant {
        "ML-DSA-44" => CKP_ML_DSA_44,
        "ML-DSA-65" => CKP_ML_DSA_65,
        "ML-DSA-87" => CKP_ML_DSA_87,
        other => return Err(key_err(format!("unknown ML-DSA variant: {other}"))),
    };
    nss_generate_key(
        CKM_ML_DSA_KEY_PAIR_GEN,
        &param_set as *const std::ffi::c_ulong as *mut std::ffi::c_void,
    )
}

// ── BackendPrivateKey NSS methods ─────────────────────────────────────────────

#[cfg(all(feature = "nss", not(feature = "openssl")))]
impl crate::crypto::BackendPrivateKey {
    /// Load a private key from PEM-encoded PKCS#8 data.
    ///
    /// Only unencrypted `PrivateKeyInfo` PEM (`BEGIN PRIVATE KEY`) is accepted.
    /// For encrypted keys use [`Self::from_pkcs8_encrypted`].
    pub fn from_pem(pem: &[u8], _password: Option<&[u8]>) -> Result<Self, PrivateKeyError> {
        let der = crate::pem::pem_blocks(pem)
            .into_iter()
            .find(|(label, _)| label == "PRIVATE KEY")
            .map(|(_, der)| der)
            .ok_or_else(|| key_err("no 'PRIVATE KEY' PEM block found"))?;
        Self::from_der(&der)
    }

    /// Load a private key from unencrypted PKCS#8 DER bytes.
    pub fn from_der(der: &[u8]) -> Result<Self, PrivateKeyError> {
        crate::pkcs8_types::PrivateKeyInfo::from_der(der)
            .map_err(|e| key_err(format!("invalid PKCS#8 DER: {e}")))?;
        let cell = std::sync::OnceLock::new();
        cell.set(der.to_vec()).expect("fresh OnceLock");
        Ok(Self {
            pkcs8_der: cell,
            spki_cache: None,
            pkcs11: None,
        })
    }

    /// Load a private key from encrypted PKCS#8 DER bytes.
    ///
    /// Not yet implemented for the NSS backend; returns an error.
    pub fn from_pkcs8_encrypted(_data: &[u8], _password: &[u8]) -> Result<Self, PrivateKeyError> {
        Err(key_err(
            "encrypted PKCS#8 import is not yet implemented for the NSS backend",
        ))
    }

    /// Serialize this key to unencrypted PEM (`BEGIN PRIVATE KEY`).
    ///
    /// `password` must be `None`; encrypted PEM export is not yet implemented
    /// for the NSS backend.
    pub fn to_pem(&self, password: Option<&[u8]>) -> Result<Vec<u8>, PrivateKeyError> {
        if password.is_some() {
            return Err(key_err(
                "encrypted PKCS#8 PEM export is not yet implemented for the NSS backend",
            ));
        }
        Ok(crate::pem::der_to_pem("PRIVATE KEY", self.pkcs8_bytes()))
    }

    /// Return a clone of the PKCS#8 DER bytes.
    pub fn to_der(&self) -> Result<Vec<u8>, PrivateKeyError> {
        Ok(self.pkcs8_bytes().to_vec())
    }

    /// Serialize to encrypted PKCS#8 DER (`EncryptedPrivateKeyInfo`).
    ///
    /// Not yet implemented for the NSS backend; returns an error.
    pub fn to_pkcs8_encrypted(&self, _password: &[u8]) -> Result<Vec<u8>, PrivateKeyError> {
        Err(key_err(
            "encrypted PKCS#8 export is not yet implemented for the NSS backend",
        ))
    }

    /// The key algorithm as a lowercase string (`"rsa"`, `"ec"`, `"ed25519"`,
    /// `"ed448"`, or `"unknown"`).
    pub fn key_type(&self) -> &'static str {
        crate::crypto::utils::key_type_from_pkcs8(self.pkcs8_bytes())
    }

    /// The key size in bits, or `None` for EdDSA and unknown algorithms.
    ///
    /// For RSA: imports the key into the NSS internal slot to read the modulus
    /// length.  For all other key types: returns `None`.
    pub fn key_bit_size(&self) -> Option<i64> {
        if self.key_type() == "rsa" {
            nss_rsa_key_bit_size(self.pkcs8_bytes())
        } else {
            None
        }
    }

    /// Extract the public key as a [`crate::crypto::BackendPublicKey`].
    pub fn public_key(&self) -> Result<crate::crypto::BackendPublicKey, PrivateKeyError> {
        let spki_der = nss_public_key_spki_der(self.pkcs8_bytes())?;
        Ok(crate::crypto::BackendPublicKey { spki_der })
    }

    /// RSA-OAEP decryption.
    pub fn rsa_oaep_decrypt(
        &self,
        ciphertext: &[u8],
        hash_alg: &str,
    ) -> Result<Vec<u8>, PrivateKeyError> {
        crate::nss_backend::nss_rsa_oaep_decrypt(self.pkcs8_bytes(), ciphertext, hash_alg)
            .map_err(PrivateKeyError::new)
    }

    /// RSA PKCS#1 v1.5 decryption.
    pub fn rsa_pkcs1v15_decrypt(&self, ciphertext: &[u8]) -> Result<Vec<u8>, PrivateKeyError> {
        crate::nss_backend::nss_rsa_pkcs1v15_decrypt(self.pkcs8_bytes(), ciphertext)
            .map_err(PrivateKeyError::new)
    }

    /// Generate a new RSA private key.
    pub fn generate_rsa(key_size: u32, public_exponent: u32) -> Result<Self, PrivateKeyError> {
        let mut params = PK11RsaGenParams {
            key_size_in_bits: key_size as std::ffi::c_int,
            pe: public_exponent as std::ffi::c_ulong,
        };
        nss_generate_key(
            CKM_RSA_PKCS_KEY_PAIR_GEN,
            &mut params as *mut PK11RsaGenParams as *mut std::ffi::c_void,
        )
    }

    /// Generate a new EC private key on the given named curve (`"P-256"`,
    /// `"P-384"`, or `"P-521"`).
    pub fn generate_ec(curve: &str) -> Result<Self, PrivateKeyError> {
        // EC params passed to NSS are the DER-encoded OID of the named curve.
        let ec_oid: &[u8] = match curve {
            "P-256" => &[0x06, 0x08, 0x2a, 0x86, 0x48, 0xce, 0x3d, 0x03, 0x01, 0x07],
            "P-384" => &[0x06, 0x05, 0x2b, 0x81, 0x04, 0x00, 0x22],
            "P-521" => &[0x06, 0x05, 0x2b, 0x81, 0x04, 0x00, 0x23],
            other => {
                return Err(key_err(format!(
                    "unsupported EC curve: '{other}'; expected P-256, P-384, or P-521"
                )))
            }
        };
        let mut params_item = SECItemStr {
            type_: SECItemType::siBuffer,
            data: ec_oid.as_ptr() as *mut _,
            len: ec_oid.len() as u32,
        };
        nss_generate_key(
            CKM_EC_KEY_PAIR_GEN,
            &mut params_item as *mut SECItemStr as *mut std::ffi::c_void,
        )
    }

    /// Generate a new Ed25519 private key.
    pub fn generate_ed25519() -> Result<Self, PrivateKeyError> {
        // OID 1.3.101.112 (id-EdDSA-Ed25519).
        let oid: &[u8] = &[0x06, 0x03, 0x2b, 0x65, 0x70];
        let mut params_item = SECItemStr {
            type_: SECItemType::siBuffer,
            data: oid.as_ptr() as *mut _,
            len: oid.len() as u32,
        };
        nss_generate_key(
            CKM_EC_EDWARDS_KEY_PAIR_GEN,
            &mut params_item as *mut SECItemStr as *mut std::ffi::c_void,
        )
    }

    /// Generate a new Ed448 private key.
    pub fn generate_ed448() -> Result<Self, PrivateKeyError> {
        // OID 1.3.101.113 (id-EdDSA-Ed448).
        let oid: &[u8] = &[0x06, 0x03, 0x2b, 0x65, 0x71];
        let mut params_item = SECItemStr {
            type_: SECItemType::siBuffer,
            data: oid.as_ptr() as *mut _,
            len: oid.len() as u32,
        };
        nss_generate_key(
            CKM_EC_EDWARDS_KEY_PAIR_GEN,
            &mut params_item as *mut SECItemStr as *mut std::ffi::c_void,
        )
    }

    /// Build an EC private key from raw scalar *d* and affine public-point
    /// coordinates *x* and *y* for the given NIST curve name.
    ///
    /// `curve` must be one of `"P-256"`, `"P-384"`, or `"P-521"`.
    /// All byte slices are big-endian unsigned integers.
    ///
    /// # Errors
    ///
    /// Returns an error if `curve` is not one of the supported names or if
    /// the DER encoding or NSS import of the key material fails.
    pub fn from_ec_private_scalar(
        d: &[u8],
        x: &[u8],
        y: &[u8],
        curve: &str,
    ) -> Result<Self, PrivateKeyError> {
        let curve_oid_components: &[u32] = match curve {
            "P-256" => crate::oids::EC_CURVE_P256,
            "P-384" => crate::oids::EC_CURVE_P384,
            "P-521" => crate::oids::EC_CURVE_P521,
            other => {
                return Err(key_err(format!(
                    "unsupported EC curve: '{other}'; expected P-256, P-384, or P-521"
                )))
            }
        };

        let pkcs8_der = nss_ec_pkcs8_from_components(d, x, y, curve_oid_components)
            .map_err(|e| key_err(e.to_string()))?;
        Self::from_der(&pkcs8_der)
    }

    /// Build an RSA private key from the standard CRT components.
    ///
    /// All byte slices in `components` are interpreted as big-endian unsigned
    /// integers (the same encoding used by JWK and PKCS#1).  Use
    /// [`crate::crypto::RsaPrivateComponents`] to supply the eight required
    /// fields: *n*, *e*, *d*, *p*, *q*, *dp*, *dq*, *qi*.
    ///
    /// # Errors
    ///
    /// Returns an error if the DER encoding or NSS import of the key material
    /// fails (e.g. inconsistent CRT values).
    pub fn from_rsa_private_components(
        components: &crate::crypto::RsaPrivateComponents<'_>,
    ) -> Result<Self, PrivateKeyError> {
        let pkcs8_der =
            nss_rsa_pkcs8_from_components(components).map_err(|e| key_err(e.to_string()))?;
        Self::from_der(&pkcs8_der)
    }
}