Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
341 changes: 57 additions & 284 deletions Cargo.lock

Large diffs are not rendered by default.

3 changes: 1 addition & 2 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,6 @@ h2 = "0.4"
# TLS
tokio-rustls = { version = "0.26", default-features = false, features = ["logging", "tls12"] }
rustls = { version = "0.23", default-features = false, features = ["std", "logging", "tls12"] }
rustls-pemfile = "2"
rcgen = { version = "0.14.10", default-features = false, features = ["pem"] }
webpki-roots = "1"
rustls-native-certs = "0.8"
Expand Down Expand Up @@ -117,7 +116,7 @@ jsonwebtoken = { version = "10", default-features = false, features = ["use_pem"
zeroize = { version = "1", features = ["derive"] }
getrandom = "0.3"
aws-lc-rs = "1.16"
spiffe = { version = "0.15", default-features = false, features = ["workload-api-jwt", "jwt-verify-rust-crypto", "tracing"] }
spiffe = { version = "0.15", default-features = false, features = ["workload-api-jwt", "jwt-verify-aws-lc-rs", "tracing"] }

# Filesystem embedding
include_dir = "0.7"
Expand Down
1 change: 0 additions & 1 deletion crates/openshell-cli/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,6 @@ hyper = { workspace = true }
hyper-util = { workspace = true }
hyper-rustls = { version = "0.27", default-features = false, features = ["native-tokio", "http1", "http2", "tls12", "logging"] }
rustls = { workspace = true }
rustls-pemfile = { workspace = true }
reqwest = { workspace = true }

# Error handling
Expand Down
10 changes: 6 additions & 4 deletions crates/openshell-cli/src/tls.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ use openshell_sdk::{AuthConfig, ClientConfig, ClientIdentity, OpenShellClient};
use rustls::{
RootCertStore,
client::danger::{HandshakeSignatureValid, ServerCertVerified, ServerCertVerifier},
pki_types::{CertificateDer, PrivateKeyDer, ServerName, UnixTime},
pki_types::{CertificateDer, PrivateKeyDer, ServerName, UnixTime, pem::PemObject},
};
use std::io::Cursor;
use std::path::PathBuf;
Expand Down Expand Up @@ -185,7 +185,9 @@ pub fn require_tls_materials(server: &str, tls: &TlsOptions) -> Result<TlsMateri

fn load_private_key(pem: &[u8]) -> Result<PrivateKeyDer<'static>> {
let mut cursor = Cursor::new(pem);
let key = rustls_pemfile::private_key(&mut cursor)
let key = PrivateKeyDer::pem_reader_iter(&mut cursor)
.next()
.transpose()
.into_diagnostic()?
.ok_or_else(|| miette::miette!("no private key found in TLS key PEM"))?;
Ok(key)
Expand All @@ -194,15 +196,15 @@ fn load_private_key(pem: &[u8]) -> Result<PrivateKeyDer<'static>> {
pub fn build_rustls_config(materials: &TlsMaterials) -> Result<rustls::ClientConfig> {
let mut roots = RootCertStore::empty();
let mut ca_cursor = Cursor::new(&materials.ca);
let ca_certs = rustls_pemfile::certs(&mut ca_cursor)
let ca_certs = CertificateDer::pem_reader_iter(&mut ca_cursor)
.collect::<Result<Vec<CertificateDer<'static>>, _>>()
.into_diagnostic()?;
for cert in ca_certs {
roots.add(cert).into_diagnostic()?;
}

let mut cert_cursor = Cursor::new(&materials.cert);
let cert_chain = rustls_pemfile::certs(&mut cert_cursor)
let cert_chain = CertificateDer::pem_reader_iter(&mut cert_cursor)
.collect::<Result<Vec<CertificateDer<'static>>, _>>()
.into_diagnostic()?;
let key = load_private_key(&materials.key)?;
Expand Down
1 change: 0 additions & 1 deletion crates/openshell-core/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,6 @@ url = { workspace = true }
uuid = { workspace = true }
ipnet = "2"
rustls = { workspace = true }
rustls-pemfile = { workspace = true }
base64 = { workspace = true }
jsonwebtoken = { workspace = true, optional = true }
zeroize = { workspace = true, optional = true }
Expand Down
7 changes: 4 additions & 3 deletions crates/openshell-core/src/driver_utils.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@

//! Utility helpers shared across compute-driver crates.

use rustls::pki_types::{CertificateDer, pem::PemObject};
use std::path::{Path, PathBuf};

use crate::proto::compute::v1::DriverSandbox;
Expand Down Expand Up @@ -473,7 +474,7 @@ pub fn read_upstream_proxy_ca_bundle_file(path: &str, label: &str) -> Result<Str
/// Validating that rustls accepts an anchor — rather than only that PEM
/// framing base64-decodes — is what makes the host-side check equivalent to
/// the guest-side one: a PEM block holding invalid DER passes
/// `rustls_pemfile::certs` but is silently dropped by
/// `CertificateDer::pem_reader_iter` but is silently dropped by
/// `RootCertStore::add_parsable_certificates`, so counting PEM blocks alone
/// would accept on the host a bundle that contributes zero anchors at runtime.
///
Expand All @@ -486,7 +487,7 @@ pub fn validate_upstream_proxy_ca_bundle_pem(
path: &str,
label: &str,
) -> Result<(), String> {
let certs: Vec<_> = rustls_pemfile::certs(&mut pem.as_bytes())
let certs: Vec<_> = CertificateDer::pem_reader_iter(&mut pem.as_bytes())
.flatten()
.collect();
if certs.is_empty() {
Expand Down Expand Up @@ -1318,7 +1319,7 @@ mod tests {

#[test]
fn ca_bundle_rejects_pem_blocks_holding_invalid_der() {
// Passes `rustls_pemfile::certs` but contributes no trust anchor, so
// Passes `CertificateDer::pem_reader_iter` but contributes no trust anchor, so
// accepting it on the host would break every guest after boot.
let err = validate_upstream_proxy_ca_bundle_pem(
"-----BEGIN CERTIFICATE-----\nAQID\n-----END CERTIFICATE-----\n",
Expand Down
8 changes: 5 additions & 3 deletions crates/openshell-core/src/grpc_client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -469,7 +469,7 @@ impl Drop for ClientSpanStatus {
mod gateway_tls_isolation_tests {
use super::*;
use rcgen::{CertificateParams, IsCa};
use rustls::pki_types::{CertificateDer, PrivateKeyDer};
use rustls::pki_types::{CertificateDer, PrivateKeyDer, pem::PemObject};
use std::io::Cursor;
use tokio::net::TcpListener;
use tokio_rustls::TlsAcceptor;
Expand Down Expand Up @@ -503,11 +503,13 @@ mod gateway_tls_isolation_tests {
}

async fn start_h2_tls_server(pki: &TestPki) -> std::net::SocketAddr {
let certificates = rustls_pemfile::certs(&mut Cursor::new(&pki.server_cert_pem))
let certificates = CertificateDer::pem_reader_iter(&mut Cursor::new(&pki.server_cert_pem))
.collect::<std::result::Result<Vec<CertificateDer<'static>>, _>>()
.unwrap();
let key: PrivateKeyDer<'static> =
rustls_pemfile::private_key(&mut Cursor::new(&pki.server_key_pem))
PrivateKeyDer::pem_reader_iter(&mut Cursor::new(&pki.server_key_pem))
.next()
.transpose()
.unwrap()
.unwrap();
let mut config = rustls::ServerConfig::builder()
Expand Down
3 changes: 2 additions & 1 deletion crates/openshell-crypto/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -110,7 +110,8 @@ rotation constraints still apply. Authentication failures expose no plaintext or
distinction between wrong keys, AAD, nonce, ciphertext, or tags.

Coverage excludes dependency-owned SSH/russh primitives, AWS SigV4 signing,
SPIFFE RustCrypto verification, and non-Rust client runtimes. AWS SDK HTTPS also
SPIFFE JWT verification, and non-Rust client runtimes. SPIFFE uses its AWS-LC
backend directly, outside the facade. AWS SDK HTTPS also
retains its dependency-owned selection. Non-security content hashes and ordinary
scheduling randomness are not migrated. No SSH or PQC capability is asserted by
the primitive capability report. OpenSSL, strict policy, module version discovery,
Expand Down
2 changes: 1 addition & 1 deletion crates/openshell-driver-vm/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ libloading = { version = "0.8", optional = true }
tar = { version = "0.4", optional = true }
flate2 = { version = "1", optional = true }
sha2 = { version = "0.10", optional = true }
zstd = { version = "0.13", optional = true }
zstd = { version = "0.14", optional = true }

[features]
# Portable test instrumentation, independent of the compute runtime.
Expand Down
22 changes: 22 additions & 0 deletions crates/openshell-driver-vm/src/driver.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12343,6 +12343,28 @@ mod tests {
let _ = fs::remove_dir_all(base);
}

#[test]
fn extracts_zstd_compressed_oci_layer() {
let temp = tempfile::tempdir().unwrap();
let archive = tar_bytes_with_file("bin/tool", b"tool contents");
let compressed = zstd::encode_all(archive.as_slice(), 0).unwrap();
let blob = temp.path().join("layer.tar.zst");
let extracted = temp.path().join("extracted");
fs::write(&blob, compressed).unwrap();

extract_layer_blob_to_dir(
&blob,
"application/vnd.oci.image.layer.v1.tar+zstd",
&extracted,
)
.unwrap();

assert_eq!(
fs::read(extracted.join("bin/tool")).unwrap(),
b"tool contents"
);
}

#[test]
fn layer_compression_from_media_type_supports_common_formats() {
assert_eq!(
Expand Down
1 change: 0 additions & 1 deletion crates/openshell-sandbox-backend/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,6 @@ prost = { workspace = true }
prost-types = { workspace = true }
rcgen = { workspace = true }
rustls = { workspace = true }
rustls-pemfile = { workspace = true }
serde = { workspace = true }
serde_json = { workspace = true }
socket2 = { workspace = true }
Expand Down
15 changes: 10 additions & 5 deletions crates/openshell-sandbox-backend/src/runtime.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ use openshell_isolation_interface::contract::{
MediationTiming, PendingDnsQuery, PendingTcpOpen, ProviderEnvironmentInstallation,
ResolvedWorkloadIdentity, SandboxContext, TcpOpenDecision, TcpOpenDenial,
};
use rustls::pki_types::{CertificateDer, pem::PemObject};
use tokio::io::{AsyncReadExt, AsyncWriteExt};
#[cfg(unix)]
use tokio::net::UnixStream;
Expand Down Expand Up @@ -658,7 +659,7 @@ pub fn validate_client_tls(tls: &SandboxTlsClientConfig) -> Result<(), BackendEr

fn tls_client_config(tls: &SandboxTlsClientConfig) -> Result<rustls::ClientConfig, BackendError> {
openshell_crypto::tls::ensure_default_provider();
let certificates = rustls_pemfile::certs(&mut tls.trust_anchor_pem.as_bytes())
let certificates = CertificateDer::pem_reader_iter(&mut tls.trust_anchor_pem.as_bytes())
.collect::<Result<Vec<_>, _>>()
.map_err(|error| {
BackendError::Descriptor(format!("parse boundary TLS CA certificate: {error}"))
Expand Down Expand Up @@ -3040,6 +3041,7 @@ mod tests {
mod protocol_errors;

use crate::isolation_protocol::exchange_frame;
use rustls::pki_types::PrivateKeyDer;
use std::path::PathBuf;
use std::pin::Pin;
use std::task::{Context, Poll};
Expand Down Expand Up @@ -4166,10 +4168,13 @@ mod tests {
openshell_crypto::tls::ensure_default_provider();
let material =
generate_sandbox_tls_material(test_session_id()).expect("generate test material");
let certificates = rustls_pemfile::certs(&mut material.certificate_chain_pem.as_bytes())
.collect::<Result<Vec<_>, _>>()
.expect("parse server certificate");
let private_key = rustls_pemfile::private_key(&mut material.private_key_pem.as_bytes())
let certificates =
CertificateDer::pem_reader_iter(&mut material.certificate_chain_pem.as_bytes())
.collect::<Result<Vec<_>, _>>()
.expect("parse server certificate");
let private_key = PrivateKeyDer::pem_reader_iter(&mut material.private_key_pem.as_bytes())
.next()
.transpose()
.expect("parse server private key")
.expect("server private key");
let mut server_config =
Expand Down
7 changes: 5 additions & 2 deletions crates/openshell-sandbox-backend/src/runtime/test_support.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ use openshell_isolation_interface::contract::{
ProviderEnvironmentInstallation, ProvisioningEvidence, ResolvedWorkloadIdentity,
SandboxContext, TcpOpenDecision,
};
use rustls::pki_types::{CertificateDer, PrivateKeyDer, pem::PemObject};
use std::collections::HashMap;
use std::pin::Pin;
use std::sync::{Arc, Mutex};
Expand Down Expand Up @@ -688,10 +689,12 @@ impl super::BoundaryTransportConnector for Connector {
fn client(peer: Peer) -> Arc<BoundaryClient> {
let session_id = "550e8400-e29b-41d4-a716-446655440000".parse().unwrap();
let material = w::generate_sandbox_tls_material(session_id).unwrap();
let certs = rustls_pemfile::certs(&mut material.certificate_chain_pem.as_bytes())
let certs = CertificateDer::pem_reader_iter(&mut material.certificate_chain_pem.as_bytes())
.collect::<Result<Vec<_>, _>>()
.unwrap();
let key = rustls_pemfile::private_key(&mut material.private_key_pem.as_bytes())
let key = PrivateKeyDer::pem_reader_iter(&mut material.private_key_pem.as_bytes())
.next()
.transpose()
.unwrap()
.unwrap();
let mut config = openshell_crypto::tls::server_builder()
Expand Down
7 changes: 5 additions & 2 deletions crates/openshell-sandbox-backend/src/runtime_routed.rs
Original file line number Diff line number Diff line change
Expand Up @@ -582,6 +582,7 @@ async fn send_reply(
mod tests {
use super::*;
use crate::boundary_protocol::{SandboxTlsMaterial, generate_sandbox_tls_material};
use rustls::pki_types::{CertificateDer, PrivateKeyDer, pem::PemObject};

/// A runtime: dial the router, name `route`, and serve `identity`'s TLS.
async fn runtime_dial(
Expand Down Expand Up @@ -614,10 +615,12 @@ mod tests {
.unwrap()
.with_no_client_auth()
.with_single_cert(
rustls_pemfile::certs(&mut identity.certificate_chain_pem.as_bytes())
CertificateDer::pem_reader_iter(&mut identity.certificate_chain_pem.as_bytes())
.collect::<Result<Vec<_>, _>>()
.unwrap(),
rustls_pemfile::private_key(&mut identity.private_key_pem.as_bytes())
PrivateKeyDer::pem_reader_iter(&mut identity.private_key_pem.as_bytes())
.next()
.transpose()
.unwrap()
.unwrap(),
)
Expand Down
1 change: 0 additions & 1 deletion crates/openshell-sandbox/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,6 @@ nix = { workspace = true, features = ["resource", "socket", "feature", "poll"] }

# TLS crypto provider install (main.rs)
rustls = { workspace = true }
rustls-pemfile = { workspace = true }
tokio-rustls = { workspace = true }

# Serialization (serde_json::json! for OCSF unmapped fields)
Expand Down
14 changes: 10 additions & 4 deletions crates/openshell-sandbox/src/boundary_server.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ mod linux {
BoundaryExec, BoundaryLoopbackConnector, BoundaryProcess, BoundaryTerminal, ExecSession,
};

use rustls::pki_types::{CertificateDer, PrivateKeyDer, pem::PemObject};
use std::fs::File;
use std::io::{self, Read, Write};
use std::mem::size_of;
Expand Down Expand Up @@ -5034,16 +5035,20 @@ mod linux {
) -> io::Result<rustls::ServerConfig> {
openshell_crypto::tls::ensure_default_provider();
let certificate_bytes = std::fs::read(&tls.certificate_chain_path)?;
let certificates = rustls_pemfile::certs(&mut certificate_bytes.as_slice())
.collect::<Result<Vec<_>, _>>()?;
let certificates = CertificateDer::pem_reader_iter(&mut certificate_bytes.as_slice())
.collect::<Result<Vec<_>, _>>()
.map_err(|error| io::Error::new(io::ErrorKind::InvalidData, error))?;
if certificates.is_empty() {
return Err(io::Error::new(
io::ErrorKind::InvalidInput,
"boundary TLS certificate chain contains no certificates",
));
}
let private_key_bytes = std::fs::read(&tls.private_key_path)?;
let private_key = rustls_pemfile::private_key(&mut private_key_bytes.as_slice())?
let private_key = PrivateKeyDer::pem_reader_iter(&mut private_key_bytes.as_slice())
.next()
.transpose()
.map_err(|error| io::Error::new(io::ErrorKind::InvalidData, error))?
.ok_or_else(|| {
io::Error::new(
io::ErrorKind::InvalidInput,
Expand Down Expand Up @@ -5558,7 +5563,8 @@ mod linux {

fn test_client_config(tls: &SandboxTlsClientConfig) -> rustls::ClientConfig {
let mut roots = rustls::RootCertStore::empty();
for certificate in rustls_pemfile::certs(&mut tls.trust_anchor_pem.as_bytes()) {
for certificate in CertificateDer::pem_reader_iter(&mut tls.trust_anchor_pem.as_bytes())
{
roots
.add(certificate.expect("parse test CA"))
.expect("add test CA");
Expand Down
1 change: 0 additions & 1 deletion crates/openshell-server/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,6 @@ http-body-util = { workspace = true }
# TLS
tokio-rustls = { workspace = true }
rustls = { workspace = true }
rustls-pemfile = { workspace = true }

# CLI
clap = { workspace = true }
Expand Down
8 changes: 5 additions & 3 deletions crates/openshell-server/src/auth/sandbox_jwt.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,10 +24,10 @@ pub use openshell_extension_core::{
EXTENSION_JWT_TYP, ExtensionAudience, ExtensionCallerKind, ExtensionJwtClaims,
MAX_EXTENSION_TOKEN_TTL,
};
use rustls::pki_types::pem::{PemObject, SectionKind};
use serde::Serialize;
use sha2::{Digest as _, Sha256};
use std::{
io::Cursor,
sync::Arc,
time::{Duration, SystemTime, UNIX_EPOCH},
};
Expand Down Expand Up @@ -534,9 +534,11 @@ impl ExtensionJwtIssuer {

impl GatewayJwks {
fn from_public_key_pem(public_key_pem: &[u8], kid: String) -> Result<Self, String> {
let item = rustls_pemfile::read_one(&mut Cursor::new(public_key_pem))
let item = <(SectionKind, Vec<u8>)>::pem_slice_iter(public_key_pem)
.next()
.transpose()
.map_err(|e| format!("failed to parse Ed25519 public key PEM for JWKS: {e}"))?;
let Some(rustls_pemfile::Item::SubjectPublicKeyInfo(der)) = item else {
let Some((SectionKind::PublicKey, der)) = item else {
return Err("Ed25519 public key PEM does not contain a PUBLIC KEY block".into());
};
let (remainder, spki) = SubjectPublicKeyInfo::from_der(der.as_ref())
Expand Down
Loading
Loading