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
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
//! TLS Configuration
//!
//! Provides an interface for parsing & verifying 2030.5 certificates, as per IEEE 2030.5 section 6.11
//!

use std::path::Path;
use std::time::Duration;

use anyhow::{bail, Result};
use hyper::client::{HttpConnector, ResponseFuture};
use hyper::{Body, Client, Request};
use hyper_openssl::HttpsConnector;
use openssl::ssl::{SslConnector, SslConnectorBuilder, SslFiletype, SslMethod, SslVerifyMode};

#[cfg(feature = "pubsub")]
use openssl::ssl::{SslAcceptor, SslAcceptorBuilder};
use x509_parser::prelude::ParsedExtension;

pub(crate) type HTTPSConnector = HttpsConnector<HttpConnector>;
pub(crate) type HTTPSClient = Client<HTTPSConnector, Body>;
pub(crate) type HTTPClient = Client<HttpConnector, Body>;
pub(crate) type TlsClientConfig = SslConnectorBuilder;

#[derive(Clone, Debug)]
pub(crate) enum ClientInner {
    Https(HTTPSClient),
    Http(HTTPClient),
}

impl ClientInner {
    pub(crate) fn request(&self, req: Request<Body>) -> ResponseFuture {
        match self {
            ClientInner::Https(c) => c.request(req),
            ClientInner::Http(c) => c.request(req),
        }
    }
}

pub(crate) fn create_client_tls_cfg(
    cert_path: impl AsRef<Path>,
    pk_path: impl AsRef<Path>,
    rootca_path: impl AsRef<Path>,
) -> Result<TlsClientConfig> {
    let mut builder = SslConnector::builder(SslMethod::tls_client())?;
    log::debug!("Setting CipherSuite");
    builder.set_cipher_list("ECDHE-ECDSA-AES128-CCM8")?;
    log::debug!("Loading Certificate File");
    builder.set_certificate_file(cert_path, SslFiletype::PEM)?;
    log::debug!("Loading Private Key File");
    builder.set_private_key_file(pk_path, SslFiletype::PEM)?;
    log::debug!("Loading Certificate Authority File");
    builder.set_ca_file(rootca_path)?;
    log::debug!("Setting verification mode");
    builder.set_verify(SslVerifyMode::PEER);
    Ok(builder)
}

pub(crate) fn create_client(
    tls_config: TlsClientConfig,
    tcp_keepalive: Option<Duration>,
) -> Client<HTTPSConnector, Body> {
    let mut http = HttpConnector::new();
    http.enforce_http(false);
    http.set_keepalive(tcp_keepalive);
    let https = HttpsConnector::with_connector(http, tls_config).unwrap();
    Client::builder().build::<HTTPSConnector, hyper::Body>(https)
}

pub(crate) fn create_http_client(tcp_keepalive: Option<Duration>) -> Client<HttpConnector, Body> {
    let mut http = HttpConnector::new();
    http.enforce_http(false);
    http.set_keepalive(tcp_keepalive);
    Client::builder().build::<HttpConnector, hyper::Body>(http)
}

#[cfg(feature = "pubsub")]
pub(crate) type TlsServerConfig = SslAcceptorBuilder;

#[cfg(feature = "pubsub")]
pub(crate) fn create_server_tls_config(
    cert_path: impl AsRef<Path>,
    pk_path: impl AsRef<Path>,
    rootca_path: impl AsRef<Path>,
) -> Result<TlsServerConfig> {
    // rust-openssl forces us to create this default config that we immediately overwrite
    // If they gave us a way to cosntruct Acceptors and Connectors from Contexts,
    // we wouldn't need to double up on configs here

    let mut builder = SslAcceptor::mozilla_intermediate(SslMethod::tls_server())?;
    log::debug!("Setting CipherSuite");
    builder.set_cipher_list("ECDHE-ECDSA-AES128-CCM8")?;
    log::debug!("Loading Certificate File");
    builder.set_certificate_file(cert_path, SslFiletype::PEM)?;
    log::debug!("Loading Private Key File");
    builder.set_private_key_file(pk_path, SslFiletype::PEM)?;
    log::debug!("Loading Certificate Authority File");
    builder.set_ca_file(rootca_path)?;
    log::debug!("Setting verification mode");
    builder.set_verify(SslVerifyMode::FAIL_IF_NO_PEER_CERT | SslVerifyMode::PEER);
    Ok(builder)
}

// The `rust-openssl` crate we use for openssl bindings currently does not expose an interface necessary to check these extensions: <https://github.com/sfackler/rust-openssl/issues/373>
//
// In the meantime, we use `x509_parser` to parse and verify that the required extensions are present, for both self-signed Client Certificates and device certificates, as per the specification.

/// Verify that the PEM encoded certificate at the given path meets IEEE 2030.5 "Device Certificate" requirements.
///
/// Newly purchased or acquired certificates in an IEEE 2030.5 certificate chain will satisfy these requirements.
///
/// Currently this function isn't called when instantiating a [`Client`]` nor a [`ClientNotifServer`], but that may change in the future.
///
///
/// A valid 'device certificate' *must* be used by a [`ClientNotifServer`], i.e. NOT a self signed certificate
/// "The use of TLS (IETF RFC 5246) requires that all hosts implementing server functionality SHALL use a
/// device certificate whereby the server presents its device certificate as part of the TLS handshake"
///
/// See section 6.11.8.3.3 for more.
///
/// [`Client`]: crate::client::Client
/// [`ClientNotifServer`]: crate::pubsub::ClientNotifServer
pub fn check_device_cert(cert_path: impl AsRef<Path>) -> Result<()> {
    let contents = std::fs::read(cert_path)?;
    let (_rem, cert) = x509_parser::pem::parse_x509_pem(&contents)?;
    let cert = cert.parse_x509()?;
    // TODO: Check Issued by & Subject name
    let exts = cert.extensions();
    let mut key_usage = false;
    let mut certificate_policies = false;
    let mut san = false;
    let mut aki = false;
    for ext in exts {
        let critical = ext.critical;
        match ext.parsed_extension() {
            // "certificates containing policy mappings MUST be rejected"
            ParsedExtension::PolicyMappings(_) => {
                bail!("Device Certificates cannot contain policy mappings.")
            }
            // "Name-constraints are not supported and certificates containing name-constraints MUST be rejected."
            ParsedExtension::NameConstraints(_) => {
                bail!("Device Certificates cannot contain name constraints.")
            }
            // TODO: What do we need to examine inside the rest of these extensions? What can we reasonably check?
            ParsedExtension::CertificatePolicies(_) => {
                if critical {
                    certificate_policies = true;
                } else {
                    bail!("CertificatePolicies extension must be critical.")
                }
            }
            ParsedExtension::SubjectAlternativeName(_) => {
                if critical {
                    san = true;
                } else {
                    bail!("SubjectAlternativeName extension must be critical")
                }
            }
            ParsedExtension::KeyUsage(_) => {
                if critical {
                    key_usage = true;
                } else {
                    bail!("KeyUsage extension must be critical.")
                }
            }
            ParsedExtension::AuthorityKeyIdentifier(_) => {
                if critical {
                    bail!("AuthorityKeyIdentifier extension cannot be critical.")
                } else {
                    aki = true;
                }
            }
            ParsedExtension::SubjectKeyIdentifier(_) => {
                if critical {
                    bail!("SubjectKeyIdentifier cannot be critical")
                }
            }
            // All other extensions constitute an invalid certificate
            // TODO: This might need to be relaxed to allow for modifications
            _ => bail!("Unexpected extension or unparsed extension encountered."),
        }
    }
    if !key_usage {
        bail!("KeyUsage extension not present")
    }
    if !certificate_policies {
        bail!("CertificatePolicies extension not present")
    }
    if !san {
        bail!("SubjectAlternativeName extension not present")
    }
    if !aki {
        bail!("AuthorityKeyIdentifier extension not present")
    }
    Ok(())
}

/// Verify that the PEM encoded certificate at the given path meets IEEE 2030.5 "Self Signed Client Certificate" requirements.
///
/// See Section 6.11.8.4.3 for more
pub fn check_self_signed_client_cert(cert_path: impl AsRef<Path>) -> Result<()> {
    let contents = std::fs::read(cert_path)?;
    let (_rem, cert) = x509_parser::pem::parse_x509_pem(&contents)?;
    let cert = cert.parse_x509()?;
    let exts = cert.extensions();
    let mut key_usage = false;
    let mut certificate_policies = false;
    // TODO: Check Issued by, Subject Name, Issuer Name, Validity, and Subject Public Key and Signature
    for ext in exts {
        let critical = ext.critical;
        match ext.parsed_extension() {
            // "certificates containing policy mappings MUST be rejected"
            ParsedExtension::PolicyMappings(_) => {
                bail!("Device Certificates cannot contain policy mappings.")
            }
            // "Name-constraints are not supported and certificates containing name-constraints MUST be rejected."
            ParsedExtension::NameConstraints(_) => {
                bail!("Device Certificates cannot contain name constraints.")
            }
            ParsedExtension::CertificatePolicies(_) => {
                if critical {
                    certificate_policies = true;
                } else {
                    bail!("CertificatePolicies extension must be critical.")
                }
            }
            ParsedExtension::KeyUsage(_) => {
                if critical {
                    key_usage = true;
                } else {
                    bail!("KeyUsage extension must be critical.")
                }
            }
            ParsedExtension::SubjectKeyIdentifier(_) => {
                if critical {
                    bail!("SubjectKeyIdentifier cannot be critical")
                }
            }
            // All other extensions constitute an invalid certificate
            // TODO: This might need to be relaxed to allow for modifications
            _ => bail!("Unexpected extension or unparsed extension encountered."),
        }
    }
    if !key_usage {
        bail!("KeyUsage extension not present.")
    }
    if !certificate_policies {
        bail!("CertificatePolicies extension not present.")
    }
    Ok(())
}

pub fn check_ca(cert_path: impl AsRef<Path>) -> Result<()> {
    let contents = std::fs::read(cert_path)?;
    let (_rem, cert) = x509_parser::pem::parse_x509_pem(&contents)?;
    let cert = cert.parse_x509()?;
    let exts = cert.extensions();
    let mut key_usage = false;
    let mut certificate_policies = false;
    let mut basic_constraints = false;
    let mut ski = false;
    for ext in exts {
        let critical = ext.critical;
        match ext.parsed_extension() {
            ParsedExtension::CertificatePolicies(_) => {
                if critical {
                    certificate_policies = true;
                } else {
                    bail!("CertificatePolicies extension must be critical.")
                }
            }
            ParsedExtension::KeyUsage(ku) => {
                if critical && ku.crl_sign() && ku.key_cert_sign() {
                    key_usage = true;
                } else {
                    bail!("KeyUsage extension must be critical and keyCertSign and crlSign must be true.")
                }
            }
            ParsedExtension::BasicConstraints(bc) => {
                if critical && bc.path_len_constraint.is_none() && bc.ca {
                    basic_constraints = true;
                } else {
                    bail!("BasicConstraints must be critical, cA must be true, and pathLen must be absent.")
                }
            }
            ParsedExtension::SubjectKeyIdentifier(_) => {
                if critical {
                    bail!("SubjectKeyIdentifier cannot be critical")
                } else {
                    ski = true;
                }
            }
            _ => bail!("Unexpected extension or unparsed extension encountered."),
        }
    }
    if !key_usage {
        bail!("KeyUsage extension not present.")
    }
    if !certificate_policies {
        bail!("CertificatePolicies extension not present.")
    }
    if !basic_constraints {
        bail!("BasicConstraints extension not present.")
    }
    if !ski {
        bail!("SubjectKeyIdentifier extension not present.")
    }
    Ok(())
}

// TODO: Should we do checks on the supplied root ca?