/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 | | } |