mls_rs_crypto_rustcrypto/x509/
writer.rs1use mls_rs_core::crypto::{CipherSuite, SignatureSecretKey};
6
7use mls_rs_identity_x509::{
8 CertificateRequestParameters, DerCertificateRequest, X509RequestWriter,
9};
10
11use spki::{
12 der::{asn1::BitString, Decode},
13 AlgorithmIdentifier, SubjectPublicKeyInfo,
14};
15
16use x509_cert::{attr::Attributes, der::Encode};
17
18use x509_cert::request::{CertReq, CertReqInfo};
19
20use crate::{ec::pub_key_from_uncompressed, ec_for_x509::pub_key_to_spki, ec_signer::EcSigner};
21
22use super::{
23 util::{build_x509_name, extension_req, object_id_for_ciphersuite, request_extensions},
24 X509Error,
25};
26
27#[derive(Debug, Clone)]
28pub struct CertificateRequestWriter {
29 cipher_suite: CipherSuite,
30 signer: EcSigner,
31 signing_key: SignatureSecretKey,
32}
33
34impl CertificateRequestWriter {
35 pub fn new(
36 cipher_suite: CipherSuite,
37 signing_key: SignatureSecretKey,
38 ) -> Result<Self, X509Error> {
39 let signer = EcSigner::new(cipher_suite).ok_or(X509Error::UnsupportedCipherSuite)?;
40
41 Ok(Self {
42 signing_key,
43 signer,
44 cipher_suite,
45 })
46 }
47
48 pub fn new_generate_key(cipher_suite: CipherSuite) -> Result<Self, X509Error> {
49 let signer = EcSigner::new(cipher_suite).ok_or(X509Error::UnsupportedCipherSuite)?;
50
51 let (secret, _) = signer.signature_key_generate()?;
52
53 Ok(Self {
54 signer,
55 signing_key: secret,
56 cipher_suite,
57 })
58 }
59
60 pub fn signing_key(&self) -> &SignatureSecretKey {
61 &self.signing_key
62 }
63}
64
65impl X509RequestWriter for CertificateRequestWriter {
66 type Error = X509Error;
67
68 fn write(
69 &self,
70 params: CertificateRequestParameters,
71 ) -> Result<DerCertificateRequest, Self::Error> {
72 let public_key = self.signer.signature_key_derive_public(&self.signing_key)?;
73
74 let ec_public_key = pub_key_from_uncompressed(&public_key, *self.signer)?;
75 let der_public_key = pub_key_to_spki(&ec_public_key)?;
76 let spki = SubjectPublicKeyInfo::from_der(&der_public_key)?;
77
78 let extensions = request_extensions(¶ms)?;
79
80 let attribute = (!extensions.is_empty())
81 .then_some(extension_req(extensions))
82 .transpose()?;
83
84 let subject = build_x509_name(¶ms.subject)?;
85
86 let info = CertReqInfo {
87 version: x509_cert::request::Version::V1,
88 subject,
89 public_key: spki,
90 attributes: attribute
91 .map(|attr| Attributes::try_from([attr]))
92 .transpose()?
93 .unwrap_or_default(),
94 };
95
96 let algorithm = AlgorithmIdentifier {
97 oid: object_id_for_ciphersuite(self.cipher_suite)?,
98 parameters: None,
99 };
100
101 let signature_data = self.signer.sign(&self.signing_key, &info.to_der()?)?;
102 let signature = BitString::from_bytes(&signature_data)?;
103
104 let req = CertReq {
105 info,
106 algorithm,
107 signature,
108 };
109
110 Ok(DerCertificateRequest::new(req.to_der()?))
111 }
112}
113
114#[cfg(test)]
115mod tests {
116 use mls_rs_core::crypto::CipherSuite;
117
118 use mls_rs_identity_x509::{
119 CertificateRequestParameters, SubjectAltName, SubjectComponent, X509RequestWriter,
120 };
121
122 use crate::{ec::test_utils::ed25519_seed_to_private_key, x509::CertificateRequestWriter};
123
124 #[test]
125 fn writing_ca_csr() {
126 test_writing_csr(true)
127 }
128
129 #[test]
130 fn writing_csr() {
131 test_writing_csr(false)
132 }
133
134 fn test_writing_csr(ca: bool) {
135 let subject_seckey = if ca {
136 include_bytes!("../../test_data/x509/root_ca/key")
137 } else {
138 include_bytes!("../../test_data/x509/leaf/key")
139 };
140
141 let subject_seckey = ed25519_seed_to_private_key(subject_seckey).into();
142
143 let writer =
144 CertificateRequestWriter::new(CipherSuite::CURVE25519_AES128, subject_seckey).unwrap();
145
146 let expected_csr = if ca {
147 include_bytes!("../../test_data/x509/root_ca/csr.der").to_vec()
148 } else {
149 include_bytes!("../../test_data/x509/leaf/csr.der").to_vec()
150 };
151
152 let common_name = if ca { "RootCA" } else { "Leaf" };
153 let alt_name = if ca { "rootca.org" } else { "leaf.org" };
154
155 let params = CertificateRequestParameters {
156 subject: vec![
157 SubjectComponent::CommonName(common_name.to_string()),
158 SubjectComponent::CountryName("CH".to_string()),
159 ],
160 subject_alt_names: vec![SubjectAltName::Dns(alt_name.to_string())],
161 is_ca: ca,
162 };
163
164 let built_csr = writer.write(params).unwrap();
165
166 assert_eq!(expected_csr, built_csr.into_vec());
167 }
168}