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
use boring2::{
pkcs12::Pkcs12,
pkey::{PKey, Private},
x509::X509,
};
/// Represents a private key and X509 cert as a client certificate.
#[derive(Debug, Clone)]
pub struct Identity {
pkey: PKey<Private>,
cert: X509,
chain: Vec<X509>,
}
impl Identity {
/// Parses a DER-formatted PKCS #12 archive, using the specified password to decrypt the key.
///
/// The archive should contain a leaf certificate and its private key, as well any intermediate
/// certificates that allow clients to build a chain to a trusted root.
/// The chain certificates should be in order from the leaf certificate towards the root.
///
/// PKCS #12 archives typically have the file extension `.p12` or `.pfx`, and can be created
/// with the OpenSSL `pkcs12` tool:
///
/// ```bash
/// openssl pkcs12 -export -out identity.pfx -inkey key.pem -in cert.pem -certfile chain_certs.pem
/// ```
///
/// # Examples
///
/// ```
/// # use std::fs::File;
/// # use std::io::Read;
/// # fn pkcs12() -> Result<(), Box<dyn std::error::Error>> {
/// let mut buf = Vec::new();
/// File::open("my-ident.pfx")?
/// .read_to_end(&mut buf)?;
/// let pkcs12 = rquest::Identity::from_pkcs12_der(&buf, "my-privkey-password")?;
/// # drop(pkcs12);
/// # Ok(())
/// # }
/// ```
///
/// # Optional
///
/// This requires the `native-tls` Cargo feature enabled.
pub fn from_pkcs12_der(buf: &[u8], pass: &str) -> crate::Result<Identity> {
let pkcs12 = Pkcs12::from_der(buf)?;
let parsed = pkcs12.parse(pass)?;
Ok(Identity {
pkey: parsed.pkey,
cert: parsed.cert,
// > The stack is the reverse of what you might expect due to the way
// > PKCS12_parse is implemented, so we need to load it backwards.
// > https://github.com/sfackler/rust-native-tls/commit/05fb5e583be589ab63d9f83d986d095639f8ec44
chain: parsed.chain.into_iter().flatten().rev().collect(),
})
}
/// Parses a chain of PEM encoded X509 certificates, with the leaf certificate first.
/// `key` is a PEM encoded PKCS #8 formatted private key for the leaf certificate.
///
/// The certificate chain should contain any intermediate certificates that should be sent to
/// clients to allow them to build a chain to a trusted root.
///
/// A certificate chain here means a series of PEM encoded certificates concatenated together.
///
/// # Examples
///
/// ```
/// # use std::fs;
/// # fn pkcs8() -> Result<(), Box<dyn std::error::Error>> {
/// let cert = fs::read("client.pem")?;
/// let key = fs::read("key.pem")?;
/// let pkcs8 = rquest::Identity::from_pkcs8_pem(&cert, &key)?;
/// # drop(pkcs8);
/// # Ok(())
/// # }
/// ```
///
/// # Optional
///
/// This requires the `native-tls` Cargo feature enabled.
pub fn from_pkcs8_pem(buf: &[u8], key: &[u8]) -> crate::Result<Identity> {
if !key.starts_with(b"-----BEGIN PRIVATE KEY-----") {
return Err(crate::error::builder("expected PKCS#8 PEM"));
}
let pkey = PKey::private_key_from_pem(key)?;
let mut cert_chain = X509::stack_from_pem(buf)?.into_iter();
let cert = cert_chain.next().ok_or_else(|| {
crate::error::builder("at least one certificate must be provided to create an identity")
})?;
let chain = cert_chain.collect();
Ok(Identity { pkey, cert, chain })
}
pub(crate) fn identity(
self,
connector: &mut boring2::ssl::SslConnectorBuilder,
) -> crate::Result<()> {
connector.set_certificate(&self.cert)?;
connector.set_private_key(&self.pkey)?;
for cert in self.chain.into_iter() {
// https://www.openssl.org/docs/manmaster/man3/SSL_CTX_add_extra_chain_cert.html
// specifies that "When sending a certificate chain, extra chain certificates are
// sent in order following the end entity certificate."
connector.add_extra_chain_cert(cert)?;
}
Ok(())
}
pub(crate) fn identity_ref(
&self,
connector: &mut boring2::ssl::SslConnectorBuilder,
) -> crate::Result<()> {
connector.set_certificate(&self.cert)?;
connector.set_private_key(&self.pkey)?;
for cert in self.chain.iter() {
// https://www.openssl.org/docs/manmaster/man3/SSL_CTX_add_extra_chain_cert.html
// specifies that "When sending a certificate chain, extra chain certificates are
// sent in order following the end entity certificate."
connector.add_extra_chain_cert(cert.to_owned())?;
}
Ok(())
}
}
#[cfg(test)]
mod test {
use super::Identity;
#[test]
fn identity_from_pkcs12_der_invalid() {
Identity::from_pkcs12_der(b"not der", "nope").unwrap_err();
}
#[test]
fn identity_from_pkcs8_pem_invalid() {
Identity::from_pkcs8_pem(b"not pem", b"not key").unwrap_err();
}
}