#![cfg(feature = "sc-tls")]
#[allow(dead_code)]
mod support;
use rcgen::ExtendedKeyUsagePurpose;
use rcgen::{CertificateParams, KeyPair};
use support::sc::{cli, read, Site};
use support::{bounded, command, failure, Files, Process};
#[tokio::test]
async fn missing_ca_rejected_before_dial() {
let files = Files::new();
let key = KeyPair::generate().unwrap();
let cert = CertificateParams::new(vec!["client".into()])
.unwrap()
.self_signed(&key)
.unwrap();
let cert = files.write("operational.pem", cert.pem());
let key = files.write("operational.key", key.serialize_pem());
let listener = tokio::net::TcpListener::bind("127.0.0.1:0").await.unwrap();
let url = format!("wss://{}", listener.local_addr().unwrap());
let mut cmd = command();
cmd.args(["--sc", "--json", "--sc-url", &url, "--sc-cert"])
.arg(cert)
.arg("--sc-key")
.arg(key)
.args([
"--sc-vmac",
"02:00:00:00:00:02",
"--sc-device-uuid",
"02020202-0202-0202-0202-020202020202",
"read",
"02:00:00:00:00:01",
"dev:123",
"object-name",
]);
let output = Process::spawn(&mut cmd).output();
tokio::pin!(output);
let (output, dialed) = bounded(async {
tokio::select! {
output = &mut output => (output, false),
accepted = listener.accept() => {
drop(accepted.unwrap());
(output.await, true)
}
}
})
.await;
assert!(!dialed, "missing explicit CA reached TCP dial");
failure(&output, "--sc-ca");
}
#[tokio::test]
async fn invalid_local_files_never_reach_live_listener() {
let site = Site::new();
let leaf = site.leaf("cli", ExtendedKeyUsagePurpose::ClientAuth, None);
let files = Files::new();
let ca = files.write("site CA é.pem", site.ca.pem());
let listener = std::net::TcpListener::bind("127.0.0.1:0").unwrap();
listener.set_nonblocking(true).unwrap();
let url = format!("wss://{}", listener.local_addr().unwrap());
let invalid_der = "-----BEGIN CERTIFICATE-----\nAQID\n-----END CERTIFICATE-----\n";
let malformed = "-----BEGIN CERTIFICATE-----\n!not-base64!\n-----END CERTIFICATE-----\n";
let invalid_ca_paths = [
std::path::PathBuf::new(),
files.0.join("absent.pem"),
files.0.clone(), files.write("empty.pem", ""),
files.write("text.pem", "not PEM"),
files.write("malformed.pem", malformed),
files.write("truncated.pem", "-----BEGIN CERTIFICATE-----\nAQID\n"),
files.write("invalid-der.pem", invalid_der),
files.write("key-only.pem", leaf.key.serialize_pem()),
files.write("mixed.pem", format!("{}{invalid_der}", site.ca.pem())),
];
for path in invalid_ca_paths {
let mut cmd = cli(&files, &url, &leaf);
cmd.arg("--sc-ca").arg(&path);
let output = Process::spawn(read(&mut cmd)).output().await;
failure(&output, "--sc-ca");
assert_eq!(
listener.accept().unwrap_err().kind(),
std::io::ErrorKind::WouldBlock,
"invalid CA reached TCP: {}",
path.display()
);
}
for (name, contents, diagnostic) in [
("operational é.pem", "".to_owned(), "TLS config error"),
("operational é.pem", malformed.to_owned(), "cert PEM"),
(
"operational é.pem",
invalid_der.to_owned(),
"TLS config error",
),
("operational é.key", "".to_owned(), "key PEM"),
("operational é.key", "not a key".to_owned(), "key PEM"),
(
"operational é.key",
KeyPair::generate().unwrap().serialize_pem(),
"TLS config error",
),
] {
let mut cmd = cli(&files, &url, &leaf);
cmd.arg("--sc-ca").arg(&ca);
files.write(name, contents);
let output = Process::spawn(read(&mut cmd)).output().await;
failure(&output, diagnostic);
assert_eq!(
listener.accept().unwrap_err().kind(),
std::io::ErrorKind::WouldBlock
);
}
let listener = tokio::net::TcpListener::from_std(listener).unwrap();
let mut cmd = cli(&files, &url, &leaf);
cmd.arg("--sc-ca").arg(ca);
let (output, ()) = tokio::join!(
Process::spawn(read(&mut cmd)).output(),
bounded(async {
drop(listener.accept().await.unwrap());
})
);
failure(&output, "TLS handshake");
}
#[tokio::test]
async fn missing_operational_credentials_and_identities_still_fail_locally() {
let base = [
"--sc",
"--json",
"--sc-ca",
"/absent/site.pem",
"--sc-url",
"wss://127.0.0.1:1",
];
for (extra, message) in [
(vec![], "--sc-cert is required"),
(vec!["--sc-cert", "/absent/cert"], "--sc-key is required"),
(
vec!["--sc-cert", "/absent/cert", "--sc-key", "/absent/key"],
"--sc-vmac is required",
),
(
vec![
"--sc-cert",
"/absent/cert",
"--sc-key",
"/absent/key",
"--sc-vmac",
"020000000002",
],
"--sc-device-uuid is required",
),
] {
let mut cmd = command();
cmd.args(base).args(extra);
failure(&Process::spawn(read(&mut cmd)).output().await, message);
}
}