summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorPradeepKiruvale <pradeepkumar.kj@softwareag.com>2022-09-05 19:10:01 +0530
committerGitHub <noreply@github.com>2022-09-05 19:10:01 +0530
commit2673aa078759801a636da642afc991fe4d3af3fc (patch)
treeff8deeaba94b0a93cf79c82c65bdcee4bf1c7921
parentf791e1636ccf0579f862d67ef6a49852011aa59c (diff)
move rust tls code from tedge connect to common place (#1373)
Signed-off-by: Pradeep Kumar K J <pradeepkumar.kj@softwareag.com> Signed-off-by: Pradeep Kumar K J <pradeepkumar.kj@softwareag.com>
-rw-r--r--Cargo.lock5
-rw-r--r--crates/common/certificate/Cargo.toml3
-rw-r--r--crates/common/certificate/src/lib.rs53
-rw-r--r--crates/common/certificate/src/parse_root_certificate.rs132
-rw-r--r--crates/core/tedge/Cargo.toml2
-rw-r--r--crates/core/tedge/src/cli/certificate/error.rs43
-rw-r--r--crates/core/tedge/src/cli/connect/c8y_direct_connection.rs153
-rw-r--r--crates/core/tedge/src/cli/connect/error.rs7
8 files changed, 206 insertions, 192 deletions
diff --git a/Cargo.lock b/Cargo.lock
index dee24d04..e55d6429 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -517,7 +517,10 @@ dependencies = [
"base64",
"pem",
"rcgen",
+ "rustls 0.19.1",
+ "rustls 0.20.6",
"sha-1 0.10.0",
+ "tempfile",
"thiserror",
"time",
"x509-parser",
@@ -2790,8 +2793,6 @@ dependencies = [
"reqwest",
"rpassword",
"rumqttc",
- "rustls 0.19.1",
- "rustls 0.20.6",
"serde",
"serde_json",
"tedge_config",
diff --git a/crates/common/certificate/Cargo.toml b/crates/common/certificate/Cargo.toml
index 1fa6f19c..d06f3c2b 100644
--- a/crates/common/certificate/Cargo.toml
+++ b/crates/common/certificate/Cargo.toml
@@ -9,6 +9,8 @@ rust-version = "1.58.1"
[dependencies]
rcgen = { version = "0.9", features = ["pem", "zeroize"] }
+rustls_0_19 = {package = "rustls", version = "0.19.0" }
+rustls = "0.20.6"
sha-1 = "0.10"
thiserror = "1.0"
time = "0.3"
@@ -20,4 +22,5 @@ anyhow = "1.0"
assert_matches = "1.5"
base64 = "0.13"
pem = "1.0"
+tempfile = "3.2"
time = {version = "0.3", features = ["macros"]}
diff --git a/crates/common/certificate/src/lib.rs b/crates/common/certificate/src/lib.rs
index ecfbd88a..bdec16a0 100644
--- a/crates/common/certificate/src/lib.rs
+++ b/crates/common/certificate/src/lib.rs
@@ -6,8 +6,8 @@ use sha1::{Digest, Sha1};
use std::path::Path;
use time::{Duration, OffsetDateTime};
use zeroize::Zeroizing;
-
pub mod device_id;
+pub mod parse_root_certificate;
pub struct PemCertificate {
pem: x509_parser::pem::Pem,
}
@@ -146,6 +146,45 @@ impl KeyCertPair {
}
}
+pub fn translate_rustls_error(err: &(dyn std::error::Error + 'static)) -> Option<CertificateError> {
+ if let Some(rustls::Error::InvalidCertificateData(inner)) = err.downcast_ref::<rustls::Error>()
+ {
+ match inner {
+ msg if msg.contains("CaUsedAsEndEntity") => Some(CertificateError::CertificateValidationFailure {
+ hint: "A CA certificate is used as an end-entity server certificate. Make sure that the certificate used is an end-entity certificate signed by CA certificate.".into(),
+ msg: msg.to_string(),
+ }),
+
+ msg if msg.contains("CertExpired") => Some(CertificateError::CertificateValidationFailure {
+ hint: "The server certificate has expired, the time it is being validated for is later than the certificate's `notAfter` time.".into(),
+ msg: msg.to_string(),
+ }),
+
+ msg if msg.contains("CertNotValidYet") => Some(CertificateError::CertificateValidationFailure {
+ hint: "The server certificate is not valid yet, the time it is being validated for is earlier than the certificate's `notBefore` time.".into(),
+ msg: msg.to_string(),
+ }),
+
+ msg if msg.contains("EndEntityUsedAsCa") => Some(CertificateError::CertificateValidationFailure {
+ hint: "An end-entity certificate is used as a server CA certificate. Make sure that the certificate used is signed by a correct CA certificate.".into(),
+ msg: msg.to_string(),
+ }),
+
+ msg if msg.contains("InvalidCertValidity") => Some(CertificateError::CertificateValidationFailure {
+ hint: "The server certificate validity period (`notBefore`, `notAfter`) is invalid, maybe the `notAfter` time is earlier than the `notBefore` time.".into(),
+ msg: msg.to_string(),
+ }),
+
+ _ => Some(CertificateError::CertificateValidationFailure {
+ hint: "Server certificate validation error.".into(),
+ msg: inner.to_string(),
+ }),
+ }
+ } else {
+ None
+ }
+}
+
#[derive(thiserror::Error, Debug)]
pub enum CertificateError {
#[error(transparent)]
@@ -162,6 +201,18 @@ pub enum CertificateError {
#[error("DeviceID Error")]
InvalidDeviceID(#[from] DeviceIdError),
+
+ #[error("Fail to parse the private key")]
+ UnknownPrivateKeyFormat,
+
+ #[error("Could not parse certificate")]
+ CertificateParseFailed,
+
+ #[error("HTTP Connection Problem: {msg} \nHint: {hint}")]
+ CertificateValidationFailure { hint: String, msg: String },
+
+ #[error(transparent)]
+ CertParse(#[from] rustls_0_19::TLSError),
}
pub struct NewCertificateConfig {
diff --git a/crates/common/certificate/src/parse_root_certificate.rs b/crates/common/certificate/src/parse_root_certificate.rs
new file mode 100644
index 00000000..38486565
--- /dev/null
+++ b/crates/common/certificate/src/parse_root_certificate.rs
@@ -0,0 +1,132 @@
+use rustls_0_19::{
+ internal::pemfile::{certs, pkcs8_private_keys, rsa_private_keys},
+ ClientConfig,
+};
+use std::{fs, fs::File, io::BufReader, path::PathBuf};
+
+use crate::CertificateError;
+use std::io::{Error, ErrorKind};
+
+pub fn create_tls_config() -> rustls_0_19::ClientConfig {
+ ClientConfig::new()
+}
+
+pub fn load_root_certs(
+ root_store: &mut rustls_0_19::RootCertStore,
+ cert_path: PathBuf,
+) -> Result<(), CertificateError> {
+ if fs::metadata(&cert_path)?.is_dir() {
+ for file_entry in fs::read_dir(cert_path)? {
+ add_root_cert(root_store, file_entry?.path())?;
+ }
+ } else {
+ add_root_cert(root_store, cert_path)?;
+ }
+ Ok(())
+}
+
+pub fn add_root_cert(
+ root_store: &mut rustls_0_19::RootCertStore,
+ cert_path: PathBuf,
+) -> Result<(), CertificateError> {
+ let f = File::open(cert_path)?;
+ let mut rd = BufReader::new(f);
+ let _ = root_store.add_pem_file(&mut rd).map(|_| ()).map_err(|()| {
+ Error::new(
+ ErrorKind::InvalidData,
+ "could not load PEM file".to_string(),
+ )
+ });
+ Ok(())
+}
+
+pub fn read_pvt_key(key_file: PathBuf) -> Result<rustls_0_19::PrivateKey, CertificateError> {
+ parse_pkcs8_key(key_file.clone()).or_else(|_| parse_rsa_key(key_file))
+}
+
+pub fn parse_pkcs8_key(key_file: PathBuf) -> Result<rustls_0_19::PrivateKey, CertificateError> {
+ let f = File::open(&key_file)?;
+ let mut key_reader = BufReader::new(f);
+ match pkcs8_private_keys(&mut key_reader) {
+ Ok(key) if !key.is_empty() => Ok(key[0].clone()),
+ _ => Err(CertificateError::UnknownPrivateKeyFormat),
+ }
+}
+
+pub fn parse_rsa_key(key_file: PathBuf) -> Result<rustls_0_19::PrivateKey, CertificateError> {
+ let f = File::open(&key_file)?;
+ let mut key_reader = BufReader::new(f);
+ match rsa_private_keys(&mut key_reader) {
+ Ok(key) if !key.is_empty() => Ok(key[0].clone()),
+ _ => Err(CertificateError::UnknownPrivateKeyFormat),
+ }
+}
+
+pub fn read_cert_chain(
+ cert_file: PathBuf,
+) -> Result<Vec<rustls_0_19::Certificate>, CertificateError> {
+ let f = File::open(cert_file)?;
+ let mut cert_reader = BufReader::new(f);
+ certs(&mut cert_reader).map_err(|_| CertificateError::CertificateParseFailed)
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+ use std::io::Write;
+ use tempfile::NamedTempFile;
+
+ #[test]
+ fn parse_private_rsa_key() {
+ let key = concat!(
+ "-----BEGIN RSA PRIVATE KEY-----\n",
+ "MC4CAQ\n",
+ "-----END RSA PRIVATE KEY-----"
+ );
+ let mut temp_file = NamedTempFile::new().unwrap();
+ temp_file.write_all(key.as_bytes()).unwrap();
+ let result = parse_rsa_key(temp_file.path().into()).unwrap();
+ let pvt_key = rustls_0_19::PrivateKey(vec![48, 46, 2, 1]);
+ assert_eq!(result, pvt_key);
+ }
+
+ #[test]
+ fn parse_private_pkcs8_key() {
+ let key = concat! {
+ "-----BEGIN PRIVATE KEY-----\n",
+ "MC4CAQ\n",
+ "-----END PRIVATE KEY-----"};
+ let mut temp_file = NamedTempFile::new().unwrap();
+ temp_file.write_all(key.as_bytes()).unwrap();
+ let result = parse_pkcs8_key(temp_file.path().into()).unwrap();
+ let pvt_key = rustls_0_19::PrivateKey(vec![48, 46, 2, 1]);
+ assert_eq!(result, pvt_key);
+ }
+
+ #[test]
+ fn parse_supported_key() {
+ let key = concat!(
+ "-----BEGIN RSA PRIVATE KEY-----\n",
+ "MC4CAQ\n",
+ "-----END RSA PRIVATE KEY-----"
+ );
+ let mut temp_file = NamedTempFile::new().unwrap();
+ temp_file.write_all(key.as_bytes()).unwrap();
+ let parsed_key = read_pvt_key(temp_file.path().into()).unwrap();
+ let expected_pvt_key = rustls_0_19::PrivateKey(vec![48, 46, 2, 1]);
+ assert_eq!(parsed_key, expected_pvt_key);
+ }
+
+ #[test]
+ fn parse_unsupported_key() {
+ let key = concat!(
+ "-----BEGIN DSA PRIVATE KEY-----\n",
+ "MC4CAQ\n",
+ "-----END DSA PRIVATE KEY-----"
+ );
+ let mut temp_file = NamedTempFile::new().unwrap();
+ temp_file.write_all(key.as_bytes()).unwrap();
+ let err = read_pvt_key(temp_file.path().into()).unwrap_err();
+ assert!(matches!(err, CertificateError::UnknownPrivateKeyFormat));
+ }
+}
diff --git a/crates/core/tedge/Cargo.toml b/crates/core/tedge/Cargo.toml
index f4f125b6..495aa091 100644
--- a/crates/core/tedge/Cargo.toml
+++ b/crates/core/tedge/Cargo.toml
@@ -23,8 +23,6 @@ hyper = { version = "0.14", default-features = false }
reqwest = { version = "0.11", default-features = false, features = ["blocking", "json", "rustls-tls", "stream"] }
rpassword = "5.0"
rumqttc = "0.10"
-rustls = "0.20.2"
-rustls_0_19 = {package = "rustls", version = "0.19.0" }
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
tedge_config = { path = "../../common/tedge_config" }
diff --git a/crates/core/tedge/src/cli/certificate/error.rs b/crates/core/tedge/src/cli/certificate/error.rs
index 8193350d..67094153 100644
--- a/crates/core/tedge/src/cli/certificate/error.rs
+++ b/crates/core/tedge/src/cli/certificate/error.rs
@@ -1,3 +1,4 @@
+use certificate::translate_rustls_error;
use reqwest::StatusCode;
use std::error::Error;
use tedge_config::FilePath;
@@ -71,9 +72,6 @@ pub enum CertError {
#[error(transparent)]
UrlParseError(#[from] url::ParseError),
- #[error("HTTP Connection Problem: {msg} \nHint: {hint}")]
- CertificateValidationFailure { hint: String, msg: String },
-
#[error(transparent)]
TedgeConfigError(#[from] TEdgeConfigError),
@@ -122,8 +120,8 @@ impl CertError {
// )
// At the last layer we have the InvalidCertificateData error which is a Box<&dyn Error> derived from WebpkiError not included anymore, just as a String
// This chain may break if underlying crates change.
-pub(crate) fn get_webpki_error_from_reqwest(err: reqwest::Error) -> CertError {
- if let Some(rustls::Error::InvalidCertificateData(inner)) = err
+pub fn get_webpki_error_from_reqwest(err: reqwest::Error) -> CertError {
+ if let Some(tls_error) = err
// get `hyper::Error::Connect`
.source()
.and_then(|err| err.source())
@@ -134,40 +132,9 @@ pub(crate) fn get_webpki_error_from_reqwest(err: reqwest::Error) -> CertError {
// This is our second `Custom`.
.and_then(|custom_error2| custom_error2.downcast_ref::<std::io::Error>())
.and_then(|custom_error2| custom_error2.get_ref())
- // Get final error type from `rustls::Error`.
- .and_then(|rustls_error| rustls_error.downcast_ref::<rustls::Error>())
+ .and_then(|err| translate_rustls_error(err))
{
- match inner {
- msg if msg.contains("CaUsedAsEndEntity") => CertError::CertificateValidationFailure {
- hint: "A CA certificate is used as an end-entity server certificate. Make sure that the certificate used is an end-entity certificate signed by CA certificate.".into(),
- msg: msg.to_string(),
- },
-
- msg if msg.contains("CertExpired") => CertError::CertificateValidationFailure {
- hint: "The server certificate has expired, the time it is being validated for is later than the certificate's `notAfter` time.".into(),
- msg: msg.to_string(),
- },
-
- msg if msg.contains("CertNotValidYet") => CertError::CertificateValidationFailure {
- hint: "The server certificate is not valid yet, the time it is being validated for is earlier than the certificate's `notBefore` time.".into(),
- msg: msg.to_string(),
- },
-
- msg if msg.contains("EndEntityUsedAsCa") => CertError::CertificateValidationFailure {
- hint: "An end-entity certificate is used as a server CA certificate. Make sure that the certificate used is signed by a correct CA certificate.".into(),
- msg: msg.to_string(),
- },
-
- msg if msg.contains("InvalidCertValidity") => CertError::CertificateValidationFailure {
- hint: "The server certificate validity period (`notBefore`, `notAfter`) is invalid, maybe the `notAfter` time is earlier than the `notBefore` time.".into(),
- msg: msg.to_string(),
- },
-
- _ => CertError::CertificateValidationFailure {
- hint: "Server certificate validation error.".into(),
- msg: inner.to_string(),
- },
- }
+ CertError::CertificateError(tls_error)
} else {
CertError::ReqwestError(err) // any other Error type than `hyper::Error`
}
diff --git a/crates/core/tedge/src/cli/connect/c8y_direct_connection.rs b/crates/core/tedge/src/cli/connect/c8y_direct_connection.rs
index 1683b4d8..51c91be8 100644
--- a/crates/core/tedge/src/cli/connect/c8y_direct_connection.rs
+++ b/crates/core/tedge/src/cli/connect/c8y_direct_connection.rs
@@ -1,17 +1,6 @@
use super::{BridgeConfig, ConnectError};
-
-use rumqttc::{
- self, certs, pkcs8_private_keys, rsa_private_keys, Client, Event, Incoming, MqttOptions,
- Outgoing, Packet, QoS, Transport,
-};
-
-use rustls_0_19::ClientConfig;
-
-use std::fs;
-use std::io::{Error, ErrorKind};
-use std::path::PathBuf;
-use std::{fs::File, io::BufReader};
-use tedge_config::FilePath;
+use certificate::parse_root_certificate::*;
+use rumqttc::{self, Client, Event, Incoming, MqttOptions, Outgoing, Packet, QoS, Transport};
// Connect directly to the c8y cloud over mqtt and publish device create message.
pub fn create_device_with_direct_connection(
@@ -27,18 +16,18 @@ pub fn create_device_with_direct_connection(
let mut mqtt_options = MqttOptions::new(bridge_config.remote_clientid.clone(), host[0], 8883);
mqtt_options.set_keep_alive(std::time::Duration::from_secs(5));
- let mut client_config = ClientConfig::new();
+ let mut tls_config = create_tls_config();
load_root_certs(
- &mut client_config.root_store,
- bridge_config.bridge_root_cert_path.clone(),
+ &mut tls_config.root_store,
+ bridge_config.bridge_root_cert_path.clone().into(),
)?;
- let pvt_key = read_pvt_key(bridge_config.bridge_keyfile.clone())?;
- let cert_chain = read_cert_chain(bridge_config.bridge_certfile.clone())?;
+ let pvt_key = read_pvt_key(bridge_config.bridge_keyfile.clone().into())?;
+ let cert_chain = read_cert_chain(bridge_config.bridge_certfile.clone().into())?;
- let _ = client_config.set_single_client_cert(cert_chain, pvt_key);
- mqtt_options.set_transport(Transport::tls_with_config(client_config.into()));
+ let _ = tls_config.set_single_client_cert(cert_chain, pvt_key);
+ mqtt_options.set_transport(Transport::tls_with_config(tls_config.into()));
let (mut client, mut connection) = Client::new(mqtt_options, 10);
@@ -106,127 +95,3 @@ fn publish_device_create_message(
)?;
Ok(())
}
-
-fn load_root_certs(
- root_store: &mut rustls_0_19::RootCertStore,
- cert_path: FilePath,
-) -> Result<(), ConnectError> {
- if fs::metadata(&cert_path)?.is_dir() {
- for file_entry in fs::read_dir(cert_path)? {
- add_root_cert(root_store, file_entry?.path())?;
- }
- } else {
- add_root_cert(root_store, cert_path.into())?;
- }
- Ok(())
-}
-
-fn add_root_cert(
- root_store: &mut rustls_0_19::RootCertStore,
- cert_path: PathBuf,
-) -> Result<(), ConnectError> {
- let f = File::open(cert_path)?;
- let mut rd = BufReader::new(f);
- let _ = root_store.add_pem_file(&mut rd).map(|_| ()).map_err(|()| {
- Error::new(
- ErrorKind::InvalidData,
- "could not load PEM file".to_string(),
- )
- });
- Ok(())
-}
-
-fn read_pvt_key(key_file: tedge_config::FilePath) -> Result<rustls_0_19::PrivateKey, ConnectError> {
- parse_pkcs8_key(key_file.clone()).or_else(|_| parse_rsa_key(key_file))
-}
-
-fn parse_pkcs8_key(
- key_file: tedge_config::FilePath,
-) -> Result<rustls_0_19::PrivateKey, ConnectError> {
- let f = File::open(&key_file)?;
- let mut key_reader = BufReader::new(f);
- match pkcs8_private_keys(&mut key_reader) {
- Ok(key) if !key.is_empty() => Ok(key[0].clone()),
- _ => Err(ConnectError::UnknownPrivateKeyFormat),
- }
-}
-
-fn parse_rsa_key(
- key_file: tedge_config::FilePath,
-) -> Result<rustls_0_19::PrivateKey, ConnectError> {
- let f = File::open(&key_file)?;
- let mut key_reader = BufReader::new(f);
- match rsa_private_keys(&mut key_reader) {
- Ok(key) if !key.is_empty() => Ok(key[0].clone()),
- _ => Err(ConnectError::UnknownPrivateKeyFormat),
- }
-}
-
-fn read_cert_chain(
- cert_file: tedge_config::FilePath,
-) -> Result<Vec<rustls_0_19::Certificate>, ConnectError> {
- let f = File::open(cert_file)?;
- let mut cert_reader = BufReader::new(f);
- certs(&mut cert_reader).map_err(|_| ConnectError::RumqttcCertificate)
-}
-
-#[cfg(test)]
-mod tests {
- use super::*;
- use std::io::Write;
- use tempfile::NamedTempFile;
-
- #[test]
- fn parse_private_rsa_key() {
- let key = concat!(
- "-----BEGIN RSA PRIVATE KEY-----\n",
- "MC4CAQ\n",
- "-----END RSA PRIVATE KEY-----"
- );
- let mut temp_file = NamedTempFile::new().unwrap();
- temp_file.write_all(key.as_bytes()).unwrap();
- let result = parse_rsa_key(temp_file.path().into()).unwrap();
- let pvt_key = rustls_0_19::PrivateKey(vec![48, 46, 2, 1]);
- assert_eq!(result, pvt_key);
- }
-
- #[test]
- fn parse_private_pkcs8_key() {
- let key = concat! {
- "-----BEGIN PRIVATE KEY-----\n",
- "MC4CAQ\n",
- "-----END PRIVATE KEY-----"};
- let mut temp_file = NamedTempFile::new().unwrap();
- temp_file.write_all(key.as_bytes()).unwrap();
- let result = parse_pkcs8_key(temp_file.path().into()).unwrap();
- let pvt_key = rustls_0_19::PrivateKey(vec![48, 46, 2, 1]);
- assert_eq!(result, pvt_key);
- }
-
- #[test]
- fn parse_supported_key() {
- let key = concat!(
- "-----BEGIN RSA PRIVATE KEY-----\n",
- "MC4CAQ\n",
- "-----END RSA PRIVATE KEY-----"
- );
- let mut temp_file = NamedTempFile::new().unwrap();
- temp_file.write_all(key.as_bytes()).unwrap();
- let parsed_key = read_pvt_key(temp_file.path().into()).unwrap();
- let expected_pvt_key = rustls_0_19::PrivateKey(vec![48, 46, 2, 1]);
- assert_eq!(parsed_key, expected_pvt_key);
- }
-
- #[test]
- fn parse_unsupported_key() {
- let key = concat!(
- "-----BEGIN DSA PRIVATE KEY-----\n",
- "MC4CAQ\n",
- "-----END DSA PRIVATE KEY-----"
- );
- let mut temp_file = NamedTempFile::new().unwrap();
- temp_file.write_all(key.as_bytes()).unwrap();
- let err = read_pvt_key(temp_file.path().into()).unwrap_err();
- assert!(matches!(err, ConnectError::UnknownPrivateKeyFormat));
- }
-}
diff --git a/crates/core/tedge/src/cli/connect/error.rs b/crates/core/tedge/src/cli/connect/error.rs
index 07ddbfbc..f3375504 100644
--- a/crates/core/tedge/src/cli/connect/error.rs
+++ b/crates/core/tedge/src/cli/connect/error.rs
@@ -44,9 +44,6 @@ pub enum ConnectError {
)]
InvalidJWTToken { token: String, reason: String },
- #[error("Fail to parse the private key")]
- UnknownPrivateKeyFormat,
-
- #[error("Could not parse certificate")]
- RumqttcCertificate,
+ #[error(transparent)]
+ CertificateError(#[from] certificate::CertificateError),
}