Skip to content
Open
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
1 change: 1 addition & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ These pipelines connect skills into end-to-end workflows. Individual skill files
| `crates/openshell-ocsf/` | OCSF logging | OCSF v1.8.0 event types, builders, shorthand/JSONL formatters, tracing layers |
| `crates/openshell-otel/` | OpenTelemetry support | Shared OTLP trace provider, resource, and tracing-layer construction |
| `crates/openshell-otel-test-support/` | OpenTelemetry test support | Shared loopback OTLP collector fixture for tracing tests |
| `crates/openshell-crypto/` | Crypto backend | Backend-neutral primitives and TLS, PKI, JWT adapters; AWS-LC implementation |
| `crates/openshell-core/` | Shared core | Common types, configuration, error handling |
| `crates/openshell-extension-core/` | Extension core | Shared extension identity, JWT claims, bearer-token rotation, and TLS transport primitives |
| `crates/openshell-gateway/` | Gateway binary composition | Links selected first-party compute drivers into the backend-agnostic server registry |
Expand Down
37 changes: 33 additions & 4 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

12 changes: 6 additions & 6 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -37,10 +37,10 @@ http-body-util = "0.1"
h2 = "0.4"

# TLS
tokio-rustls = { version = "0.26", default-features = false, features = ["logging", "tls12", "aws_lc_rs"] }
rustls = { version = "0.23", default-features = false, features = ["std", "logging", "tls12", "aws_lc_rs"] }
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.13", default-features = false, features = ["crypto", "pem", "aws_lc_rs", "x509-parser"] }
rcgen = { version = "0.13", default-features = false, features = ["crypto", "pem", "x509-parser"] }
webpki-roots = "1"
rustls-native-certs = "0.8"

Expand Down Expand Up @@ -107,7 +107,7 @@ base64 = "0.22"
# Crypto / Auth
sha2 = "0.10"
rand = "0.9"
jsonwebtoken = { version = "10", features = ["aws_lc_rs"] }
jsonwebtoken = { version = "10", default-features = false, features = ["use_pem"] }
zeroize = { version = "1", features = ["derive"] }
getrandom = "0.3"
aws-lc-rs = "1.16"
Expand All @@ -130,12 +130,12 @@ url = "2"
indexmap = "2"

# Database
sqlx = { version = "0.9", default-features = false, features = ["runtime-tokio", "tls-rustls-aws-lc-rs", "postgres", "sqlite", "migrate", "macros"] }
sqlx = { version = "0.9", default-features = false, features = ["runtime-tokio", "postgres", "sqlite", "migrate", "macros"] }
# SQLx's facade couples native roots to ring; select native roots independently.
sqlx-core = { version = "0.9", default-features = false, features = ["rustls-native-certs"] }

# Kubernetes
kube = { version = "0.99", default-features = false, features = ["client", "runtime", "derive", "rustls-tls", "aws-lc-rs"] }
kube = { version = "0.99", default-features = false, features = ["client", "runtime", "derive", "rustls-tls"] }
kube-runtime = "0.99"
k8s-openapi = { version = "0.24", features = ["v1_29"] }

Expand Down
21 changes: 21 additions & 0 deletions architecture/build.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,27 @@ Sandbox community images are built outside this repository.

## Build Features

Rust builds require Rust 1.94 or newer. TLS and certificate generation use
AWS-LC, including the CLI and standalone examples. Native and cross-build
environments must provide the C toolchain required by aws-lc-sys; the Nix
development shells provide static AWS-LC libraries.

First-party crypto selection lives in `openshell-crypto`: backend-neutral
primitive traits and protocol adapters preserve existing TLS, PKI, JWT, and
credential-storage behavior. Application crates enable integration features on
that crate instead of naming a backend. Process-default initialization preserves
existing embedder providers; context capability reporting does not attest those
globals or dependency-owned crypto. OpenSSL and strict FIPS operation are separate
follow-up work. The crate README defines extension and coverage boundaries.
Digest operations propagate backend failures through JWT and credential key-ID
generation rather than requiring infallible provider operations.

SQLx uses AWS-LC with native certificate roots.
The server enables
`sqlx-core/rustls-native-certs` directly because SQLx's facade does not expose
that root selection independently of the crypto provider. Credential storage
continues to use the same AES-256-GCM envelope format across backend changes.

Anonymous telemetry emission is gated behind a default-on `telemetry` Cargo
feature. It is defined in `openshell-core` (where the emission code, HTTP
client, and endpoint live) and forwarded by the binary crates that emit or
Expand Down
2 changes: 1 addition & 1 deletion crates/openshell-bootstrap/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,11 @@ repository.workspace = true
rust-version.workspace = true

[dependencies]
openshell-crypto = { path = "../openshell-crypto" }
openshell-core = { path = "../openshell-core", default-features = false }
bytes = { workspace = true }
miette = { workspace = true }
rcgen = { workspace = true }
sha2 = { workspace = true }
serde = { workspace = true }
serde_json = { workspace = true }
tempfile = "3"
Expand Down
22 changes: 11 additions & 11 deletions crates/openshell-bootstrap/src/jwt.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,6 @@
//! validator and any future external verifiers.

use miette::{IntoDiagnostic, Result, WrapErr};
use rcgen::{KeyPair, PKCS_ED25519};
use sha2::{Digest, Sha256};

/// All PEM-encoded material needed to mint and validate sandbox JWTs.
///
Expand All @@ -39,12 +37,12 @@ pub struct JwtKeyMaterial {
/// (validation), so the gateway can round-trip its own tokens with no
/// further conversion.
pub fn generate_jwt_key() -> Result<JwtKeyMaterial> {
let keypair = KeyPair::generate_for(&PKCS_ED25519)
let keypair = openshell_crypto::pki::generate_jwt_keypair()
.into_diagnostic()
.wrap_err("failed to generate Ed25519 JWT signing key")?;
let signing_key_pem = keypair.serialize_pem();
let public_key_pem = keypair.public_key_pem();
let kid = kid_from_public_key_der(&keypair.public_key_der());
let kid = kid_from_public_key_der(&keypair.public_key_der())?;
Ok(JwtKeyMaterial {
signing_key_pem,
public_key_pem,
Expand All @@ -57,9 +55,11 @@ pub fn generate_jwt_key() -> Result<JwtKeyMaterial> {
/// First 16 bytes hex-encoded — collision-resistant for the small N of
/// signing keys a single deployment ever has, while staying short enough
/// to keep JWT headers compact.
fn kid_from_public_key_der(public_key_der: &[u8]) -> String {
let digest = Sha256::digest(public_key_der);
hex_encode_prefix(&digest, 16)
fn kid_from_public_key_der(public_key_der: &[u8]) -> Result<String> {
let digest = openshell_crypto::sha256(public_key_der)
.into_diagnostic()
.wrap_err("failed to derive JWT signing key ID")?;
Ok(hex_encode_prefix(&digest, 16))
}

fn hex_encode_prefix(bytes: &[u8], n: usize) -> String {
Expand Down Expand Up @@ -87,15 +87,15 @@ mod tests {
#[test]
fn kid_is_stable_for_identical_public_keys() {
// Same input -> same kid. Hash of a fixed byte string.
let kid_a = kid_from_public_key_der(b"abc");
let kid_b = kid_from_public_key_der(b"abc");
let kid_a = kid_from_public_key_der(b"abc").unwrap();
let kid_b = kid_from_public_key_der(b"abc").unwrap();
assert_eq!(kid_a, kid_b);
}

#[test]
fn kid_differs_for_different_public_keys() {
let kid_a = kid_from_public_key_der(b"first");
let kid_b = kid_from_public_key_der(b"second");
let kid_a = kid_from_public_key_der(b"first").unwrap();
let kid_b = kid_from_public_key_der(b"second").unwrap();
assert_ne!(kid_a, kid_b);
}

Expand Down
27 changes: 13 additions & 14 deletions crates/openshell-bootstrap/src/pki.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
use crate::jwt::{JwtKeyMaterial, generate_jwt_key};
use miette::{IntoDiagnostic, Result, WrapErr};
use rcgen::{
BasicConstraints, CertificateParams, DnType, Ia5String, IsCa, KeyPair, KeyUsagePurpose, SanType,
BasicConstraints, CertificateParams, DnType, Ia5String, IsCa, KeyUsagePurpose, SanType,
};
use std::net::IpAddr;

Expand Down Expand Up @@ -54,7 +54,7 @@ pub const DEFAULT_SERVER_SANS: &[&str] = &[
/// are ephemeral to the cluster's lifetime.
pub fn generate_pki(extra_sans: &[String]) -> Result<PkiBundle> {
// --- CA ---
let ca_key = KeyPair::generate()
let ca_key = openshell_crypto::pki::generate_keypair()
.into_diagnostic()
.wrap_err("failed to generate CA key")?;
let mut ca_params = CertificateParams::new(Vec::<String>::new())
Expand All @@ -69,13 +69,12 @@ pub fn generate_pki(extra_sans: &[String]) -> Result<PkiBundle> {
.distinguished_name
.push(DnType::CommonName, "openshell-ca");

let ca_cert = ca_params
.self_signed(&ca_key)
let ca_cert = openshell_crypto::pki::self_signed(ca_params, &ca_key)
.into_diagnostic()
.wrap_err("failed to self-sign CA certificate")?;

// --- Server cert ---
let server_key = KeyPair::generate()
let server_key = openshell_crypto::pki::generate_keypair()
.into_diagnostic()
.wrap_err("failed to generate server key")?;
let server_sans = build_server_sans(extra_sans);
Expand All @@ -88,13 +87,13 @@ pub fn generate_pki(extra_sans: &[String]) -> Result<PkiBundle> {
.distinguished_name
.push(DnType::CommonName, "openshell-server");

let server_cert = server_params
.signed_by(&server_key, &ca_cert, &ca_key)
.into_diagnostic()
.wrap_err("failed to sign server certificate")?;
let server_cert =
openshell_crypto::pki::signed_by(server_params, &server_key, &ca_cert, &ca_key)
.into_diagnostic()
.wrap_err("failed to sign server certificate")?;

// --- Client cert (shared by CLI and sandbox pods) ---
let client_key = KeyPair::generate()
let client_key = openshell_crypto::pki::generate_keypair()
.into_diagnostic()
.wrap_err("failed to generate client key")?;
let mut client_params = CertificateParams::new(Vec::<String>::new())
Expand All @@ -108,10 +107,10 @@ pub fn generate_pki(extra_sans: &[String]) -> Result<PkiBundle> {
.distinguished_name
.push(DnType::OrganizationalUnitName, "openshell-user");

let client_cert = client_params
.signed_by(&client_key, &ca_cert, &ca_key)
.into_diagnostic()
.wrap_err("failed to sign client certificate")?;
let client_cert =
openshell_crypto::pki::signed_by(client_params, &client_key, &ca_cert, &ca_key)
.into_diagnostic()
.wrap_err("failed to sign client certificate")?;

// --- JWT signing key (Ed25519, used to mint per-sandbox identity tokens) ---
let JwtKeyMaterial {
Expand Down
3 changes: 2 additions & 1 deletion crates/openshell-cli/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ name = "openshell"
path = "src/main.rs"

[dependencies]
openshell-crypto = { path = "../openshell-crypto", features = ["tls-hyper"] }
openshell-bootstrap = { path = "../openshell-bootstrap" }
openshell-core = { path = "../openshell-core", default-features = false }
openshell-policy = { path = "../openshell-policy" }
Expand Down Expand Up @@ -49,7 +50,7 @@ bytes = { workspace = true }
http-body-util = { workspace = true }
hyper = { workspace = true }
hyper-util = { workspace = true }
hyper-rustls = { version = "0.27", default-features = false, features = ["native-tokio", "http1", "http2", "tls12", "logging", "aws-lc-rs"] }
hyper-rustls = { version = "0.27", default-features = false, features = ["native-tokio", "http1", "http2", "tls12", "logging"] }
rustls = { workspace = true }
rustls-pemfile = { workspace = true }
tokio-rustls = { workspace = true }
Expand Down
2 changes: 1 addition & 1 deletion crates/openshell-cli/src/oidc_auth.rs
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,7 @@ async fn discover(issuer: &str, insecure: bool) -> Result<OidcDiscovery> {
}

fn http_client(insecure: bool) -> reqwest::Client {
let _ = rustls::crypto::aws_lc_rs::default_provider().install_default();
openshell_crypto::tls::ensure_default_provider();
let mut builder = reqwest::ClientBuilder::new().redirect(reqwest::redirect::Policy::none());
if insecure {
builder = builder.danger_accept_invalid_certs(true);
Expand Down
Loading
Loading