third_wheel/
certificates.rs

1use log::debug;
2use std::io;
3use std::{fs::File, path::Path};
4
5use openssl::asn1::Asn1Time;
6use openssl::bn::{BigNum, MsbOption};
7use openssl::hash::MessageDigest;
8use openssl::pkcs12::Pkcs12;
9use openssl::pkey::{PKey, Private};
10use openssl::rsa::Rsa;
11use openssl::stack::Stack;
12use openssl::x509::extension::SubjectAlternativeName;
13use openssl::x509::{GeneralNameRef, X509Name, X509NameBuilder, X509NameRef, X509};
14
15use crate::error::Error;
16
17/// A certificate authority to use for impersonating websites during the
18/// man-in-the-middle. The client must trust the given certificate for it to
19/// trust the proxy.
20pub struct CertificateAuthority {
21    /// the certificate authority's self-signed certificate
22    pub cert: X509,
23    /// the private signing key for the certificate authority
24    pub key: PKey<Private>,
25}
26
27impl CertificateAuthority {
28    /// Load certificate authority from PEM formatted files. The key file must
29    /// not require a passphrase (e.g. was created with the `-nodes` option on
30    /// openssl). NB: There is a bug/behaviour in Mac OS X that prevents opening
31    /// unencrypted key files.
32    pub fn load_from_pem_files<P: AsRef<Path>, Q: AsRef<Path>>(
33        cert_file: P,
34        key_file: Q,
35    ) -> Result<Self, Error> {
36        let cert = X509::from_pem(&get_bytes_from_file(cert_file)?)?;
37
38        let key = get_bytes_from_file(key_file)?;
39        let key = PKey::from_rsa(Rsa::private_key_from_pem(&key)?)?;
40
41        Ok(Self { cert, key })
42    }
43
44    /// Load certificate authority from PEM formatted files where the key file
45    /// is encrypted with a passphrase
46    pub fn load_from_pem_files_with_passphrase_on_key<P: AsRef<Path>, Q: AsRef<Path>>(
47        cert_file: P,
48        key_file: Q,
49        passphrase: &str,
50    ) -> Result<Self, Error> {
51        let cert = X509::from_pem(&get_bytes_from_file(cert_file)?)?;
52
53        let key = get_bytes_from_file(key_file)?;
54        let key = PKey::from_rsa(Rsa::private_key_from_pem_passphrase(
55            &key,
56            passphrase.as_bytes(),
57        )?)?;
58
59        Ok(Self { cert, key })
60    }
61}
62
63fn get_bytes_from_file<P: AsRef<Path>>(path: P) -> Result<Vec<u8>, Error> {
64    let mut file = File::open(path)?;
65    let mut bytes: Vec<u8> = vec![];
66    io::copy(&mut file, &mut bytes)?;
67    Ok(bytes)
68}
69
70pub(crate) fn native_identity(
71    certificate: &X509,
72    key: &PKey<Private>,
73) -> Result<native_tls::Identity, Error> {
74    let pkcs = Pkcs12::builder()
75        .build(&"third-wheel", &"", key, certificate)?
76        .to_der()?;
77    let identity = native_tls::Identity::from_pkcs12(&pkcs, &"third-wheel")?;
78    Ok(identity)
79}
80
81/// Sign a certificate for this domain
82///
83/// This function does not intelligently spoof fields like in the mitm proxy because
84/// it does not call the actual domain to get that information. As such, this may be
85/// rejected by browsers.
86pub fn create_signed_certificate_for_domain(
87    domain: &str,
88    ca: &CertificateAuthority,
89) -> Result<X509, Error> {
90    let mut cert_builder = X509::builder()?;
91
92    let mut host_name = X509Name::builder()?;
93    host_name.append_entry_by_text("CN", domain)?;
94    let host_name = host_name.build();
95
96    cert_builder.set_subject_name(&host_name)?;
97    cert_builder.set_version(2)?;
98    cert_builder.set_not_before((Asn1Time::days_from_now(0)?).as_ref())?;
99    cert_builder.set_not_after((Asn1Time::days_from_now(365)?).as_ref())?;
100
101    let serial_number = {
102        let mut serial_number = BigNum::new()?;
103        serial_number.rand(159, MsbOption::MAYBE_ZERO, false)?;
104        serial_number.to_asn1_integer()?
105    };
106    cert_builder.set_serial_number(&serial_number)?;
107
108    let subject_alternative_name = SubjectAlternativeName::new()
109        .dns(domain)
110        .build(&cert_builder.x509v3_context(Some(&ca.cert), None))?;
111    cert_builder.append_extension(subject_alternative_name)?;
112
113    cert_builder.set_issuer_name(&ca.cert.issuer_name())?;
114    cert_builder.set_pubkey(&ca.key)?;
115    cert_builder.sign(&ca.key, MessageDigest::sha256())?;
116
117    Ok(cert_builder.build())
118}
119
120fn copy_name(in_name: &X509NameRef) -> Result<X509Name, Error> {
121    let mut copy: X509NameBuilder = X509Name::builder()?;
122    for entry in in_name.entries() {
123        copy.append_entry_by_nid(
124            entry.object().nid(),
125            entry
126                .data()
127                .as_utf8()
128                .expect("Expected string as entry in name")
129                .as_ref(),
130        )
131        .expect("Failed to add entry by nid");
132    }
133
134    Ok(copy.build())
135}
136
137fn copy_alt_names(in_cert: &X509) -> Option<SubjectAlternativeName> {
138    match in_cert.subject_alt_names() {
139        Some(in_alt_names) => {
140            let mut subject_alternative_name = SubjectAlternativeName::new();
141            for gn in in_alt_names {
142                if let Some(email) = gn.email() {
143                    subject_alternative_name.email(email);
144                } else if let Some(dns) = gn.dnsname() {
145                    subject_alternative_name.dns(dns);
146                } else if let Some(uri) = gn.uri() {
147                    subject_alternative_name.uri(uri);
148                } else if let Some(ipaddress) = gn.ipaddress() {
149                    //TODO: The openssl library exposes .ipaddress -> &[u8] and the builder wants &str
150                    //TODO: I have no idea whether this is u8 ascii representation of the ip or what so
151                    //TODO: lets just go with it for now.
152                    subject_alternative_name.ip(&String::from_utf8(ipaddress.to_vec())
153                        .expect("ip address on certificate is not formatted as ascii"));
154                }
155            }
156            Some(subject_alternative_name)
157        }
158        None => None,
159    }
160}
161
162pub(crate) fn spoof_certificate(
163    certificate: &X509,
164    ca: &CertificateAuthority,
165) -> Result<X509, Error> {
166    let mut cert_builder = X509::builder()?;
167
168    let name: &X509NameRef = certificate.subject_name();
169    let host_name = copy_name(name)?;
170    cert_builder.set_subject_name(&host_name)?;
171    cert_builder.set_not_before(certificate.not_before())?;
172    cert_builder.set_not_after(certificate.not_after())?;
173
174    cert_builder.set_serial_number(certificate.serial_number())?;
175
176    cert_builder.set_version(2)?;
177
178    if let Some(subject_alternative_name) = copy_alt_names(certificate) {
179        let subject_alternative_name =
180            subject_alternative_name.build(&cert_builder.x509v3_context(Some(&ca.cert), None))?;
181        cert_builder.append_extension(subject_alternative_name)?;
182    }
183
184    cert_builder.set_issuer_name(&ca.cert.issuer_name())?;
185    cert_builder.set_pubkey(&ca.key)?;
186    cert_builder.sign(&ca.key, MessageDigest::sha256())?;
187
188    Ok(cert_builder.build())
189}
190
191#[allow(dead_code)]
192fn print_certificate(certificate: &X509) {
193    debug!("New certificate");
194
195    debug!("subject_name:");
196    for entry in certificate.subject_name().entries() {
197        debug!("{}: {}", entry.object(), entry.data().as_utf8().unwrap());
198    }
199    debug!("issuer_name:");
200    for entry in certificate.issuer_name().entries() {
201        debug!("{}: {}", entry.object(), entry.data().as_utf8().unwrap());
202    }
203
204    debug!("subject_alt_names");
205    for general_name in certificate
206        .subject_alt_names()
207        .unwrap_or_else(|| Stack::new().unwrap())
208        .iter()
209    {
210        print_general_name(general_name);
211    }
212
213    debug!("issuer_alt_names");
214    for general_name in certificate
215        .issuer_alt_names()
216        .unwrap_or_else(|| Stack::new().unwrap())
217        .iter()
218    {
219        print_general_name(general_name);
220    }
221
222    debug!("public_key: {:?}", certificate.public_key());
223
224    debug!("not_after: {}", certificate.not_after());
225    debug!("not_before: {}", certificate.not_before());
226
227    debug!("Signature: ");
228    debug!("{:x?}", certificate.signature().as_slice());
229
230    debug!(
231        "Signature algorithm: {}",
232        certificate.signature_algorithm().object()
233    );
234
235    debug!("ocsp_responders:");
236    let responders = certificate.ocsp_responders();
237    match responders {
238        Ok(stack) => {
239            for responder in stack.iter() {
240                debug!("{:?}", responder);
241            }
242        }
243        Err(err) => debug!("Responders threw error: {}", err),
244    }
245
246    let serial_number = certificate.serial_number().to_bn();
247    match serial_number {
248        Ok(sn) => debug!("{}", sn),
249        Err(err) => debug!("Responders threw error: {}", err),
250    }
251}
252
253fn print_general_name(general_name: &GeneralNameRef) {
254    if let Some(email) = general_name.email() {
255        debug!("email: {}", email);
256    }
257    if let Some(dnsname) = general_name.dnsname() {
258        debug!("dnsname: {}", dnsname);
259    }
260    if let Some(uri) = general_name.uri() {
261        debug!("uri: {}", uri);
262    }
263    if let Some(ipaddress) = general_name.ipaddress() {
264        debug!("ipaddress: {:?}", ipaddress);
265    }
266}