Coverage Report

Created: 2026-04-14 06:46

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/ztunnel/src/tls/csr.rs
Line
Count
Source
1
// Copyright Istio Authors
2
//
3
// Licensed under the Apache License, Version 2.0 (the "License");
4
// you may not use this file except in compliance with the License.
5
// You may obtain a copy of the License at
6
//
7
//     http://www.apache.org/licenses/LICENSE-2.0
8
//
9
// Unless required by applicable law or agreed to in writing, software
10
// distributed under the License is distributed on an "AS IS" BASIS,
11
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
// See the License for the specific language governing permissions and
13
// limitations under the License.
14
15
use crate::tls::Error;
16
17
pub struct CertSign {
18
    pub csr: String,
19
    pub private_key: Vec<u8>,
20
}
21
22
pub struct CsrOptions {
23
    pub san: String,
24
}
25
26
impl CsrOptions {
27
    #[cfg(feature = "tls-boring")]
28
    pub fn generate(&self) -> Result<CertSign, Error> {
29
        use boring::ec::{EcGroup, EcKey};
30
        use boring::hash::MessageDigest;
31
        use boring::nid::Nid;
32
        use boring::pkey::PKey;
33
        use boring::stack::Stack;
34
        use boring::x509::extension::SubjectAlternativeName;
35
        use boring::x509::{self};
36
        // TODO: https://github.com/rustls/rcgen/issues/228 can we always use rcgen?
37
38
        let group = EcGroup::from_curve_name(Nid::X9_62_PRIME256V1)?;
39
        let ec_key = EcKey::generate(&group)?;
40
        let pkey = PKey::from_ec_key(ec_key)?;
41
42
        let mut csr = x509::X509ReqBuilder::new()?;
43
        csr.set_pubkey(&pkey)?;
44
        let mut extensions = Stack::new()?;
45
        let subject_alternative_name = SubjectAlternativeName::new()
46
            .uri(&self.san)
47
            .critical()
48
            .build(&csr.x509v3_context(None))?;
49
50
        extensions.push(subject_alternative_name)?;
51
        csr.add_extensions(&extensions)?;
52
        csr.sign(&pkey, MessageDigest::sha256())?;
53
54
        let csr = csr.build();
55
        let pkey_pem = pkey.private_key_to_pem_pkcs8()?;
56
        let csr_pem = csr.to_pem()?;
57
        let csr_pem = std::str::from_utf8(&csr_pem)
58
            .expect("CSR is valid string")
59
            .to_string();
60
        Ok(CertSign {
61
            csr: csr_pem,
62
            private_key: pkey_pem,
63
        })
64
    }
65
66
    #[cfg(any(feature = "tls-ring", feature = "tls-aws-lc"))]
67
0
    pub fn generate(&self) -> Result<CertSign, Error> {
68
        use rcgen::{CertificateParams, DistinguishedName, SanType};
69
0
        let kp = rcgen::KeyPair::generate_for(&rcgen::PKCS_ECDSA_P256_SHA256)?;
70
0
        let private_key = kp.serialize_pem();
71
0
        let mut params = CertificateParams::default();
72
0
        params.subject_alt_names = vec![SanType::URI(self.san.clone().try_into()?)];
73
0
        params.key_identifier_method = rcgen::KeyIdMethod::Sha256;
74
        // Avoid setting CN. rcgen defaults it to "rcgen self signed cert" which we don't want
75
0
        params.distinguished_name = DistinguishedName::new();
76
0
        let csr = params.serialize_request(&kp)?.pem()?;
77
78
0
        Ok(CertSign {
79
0
            csr,
80
0
            private_key: private_key.into(),
81
0
        })
82
0
    }
83
84
    #[cfg(feature = "tls-openssl")]
85
    pub fn generate(&self) -> Result<CertSign, Error> {
86
        use openssl::ec::{EcGroup, EcKey};
87
        use openssl::hash::MessageDigest;
88
        use openssl::nid::Nid;
89
        use openssl::pkey::PKey;
90
        use openssl::stack::Stack;
91
        use openssl::x509::extension::SubjectAlternativeName;
92
        use openssl::x509::{self};
93
        // TODO: https://github.com/rustls/rcgen/issues/228 can we always use rcgen?
94
95
        let group = EcGroup::from_curve_name(Nid::X9_62_PRIME256V1)?;
96
        let ec_key = EcKey::generate(&group)?;
97
        let pkey = PKey::from_ec_key(ec_key)?;
98
99
        let mut csr = x509::X509ReqBuilder::new()?;
100
        csr.set_pubkey(&pkey)?;
101
        let mut extensions = Stack::new()?;
102
        let subject_alternative_name = SubjectAlternativeName::new()
103
            .uri(&self.san)
104
            .critical()
105
            .build(&csr.x509v3_context(None))?;
106
107
        extensions.push(subject_alternative_name)?;
108
        csr.add_extensions(&extensions)?;
109
        csr.sign(&pkey, MessageDigest::sha256())?;
110
111
        let csr = csr.build();
112
        let pkey_pem = pkey.private_key_to_pem_pkcs8()?;
113
        let csr_pem = csr.to_pem()?;
114
        let csr_pem = std::str::from_utf8(&csr_pem)
115
            .expect("CSR is valid string")
116
            .to_string();
117
        Ok(CertSign {
118
            csr: csr_pem,
119
            private_key: pkey_pem,
120
        })
121
    }
122
}
123
124
#[cfg(test)]
125
mod tests {
126
    use crate::tls;
127
    use itertools::Itertools;
128
129
    #[test]
130
    fn test_csr() {
131
        use x509_parser::prelude::*;
132
        let csr = tls::csr::CsrOptions {
133
            san: "spiffe://td/ns/ns1/sa/sa1".to_string(),
134
        }
135
        .generate()
136
        .unwrap();
137
138
        let (_, der) = x509_parser::pem::parse_x509_pem(csr.csr.as_bytes()).unwrap();
139
140
        let (_, cert) =
141
            x509_parser::certification_request::X509CertificationRequest::from_der(&der.contents)
142
                .unwrap();
143
        cert.verify_signature().unwrap();
144
        let subject = cert.certification_request_info.subject.iter().collect_vec();
145
        assert_eq!(subject.len(), 0);
146
        let attr = cert
147
            .certification_request_info
148
            .iter_attributes()
149
            .next()
150
            .unwrap();
151
152
        let ParsedCriAttribute::ExtensionRequest(parsed) = attr.parsed_attribute() else {
153
            panic!("not a ExtensionRequest")
154
        };
155
        let ext = parsed.clone().extensions;
156
        assert_eq!(ext.len(), 1);
157
        let ext = ext.into_iter().next().unwrap();
158
        assert!(ext.critical);
159
        let ParsedExtension::SubjectAlternativeName(san) = ext.parsed_extension() else {
160
            panic!("not a SubjectAlternativeName")
161
        };
162
        assert_eq!(
163
            &format!("{san:?}"),
164
            "SubjectAlternativeName { general_names: [URI(\"spiffe://td/ns/ns1/sa/sa1\")] }"
165
        )
166
    }
167
}