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
4 changes: 3 additions & 1 deletion .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,9 @@ jobs:
run: pnpm install

- name: Prettier
run: pnpm prettier:check
run: |
pnpm prettier:check --version
pnpm prettier:check

- name: Install GTK
run: sudo apt-get update && sudo apt-get install libgtk-3-dev libjavascriptcoregtk-4.1-dev libwebkit2gtk-4.1-dev
Expand Down
37 changes: 22 additions & 15 deletions Cargo.lock

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

4 changes: 4 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -139,3 +139,7 @@ axum-server = "0.7.1"
rustls = "0.23.17"
rustls-pemfile = "2.2.0"
clap = "4.5.21"

# Testing
tempfile = "3.25.0"
tower = "0.5.3"
2 changes: 1 addition & 1 deletion crates/sage-cli/src/rpc.rs
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ impl_endpoints! {
pub async fn handle(self, path: PathBuf) -> anyhow::Result<()> {
match self {
Self::Start => {
let mut sage = Sage::new(&path);
let mut sage = Sage::new(&path, false);
let mut receiver = sage.initialize().await?;
sage.switch_wallet().await?;
tokio::spawn(async move { while let Some(_message) = receiver.recv().await {} });
Expand Down
9 changes: 9 additions & 0 deletions crates/sage-rpc/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -29,3 +29,12 @@ rustls-pemfile = { workspace = true }
utoipa = "5.2.0"
serde_json = { workspace = true }
indexmap = { workspace = true }

[dev-dependencies]
tempfile = { workspace = true }
tower = { workspace = true, features = ["util"] }
rand = { workspace = true }
rand_chacha = { workspace = true }
bip39 = { workspace = true }
chia-wallet-sdk = { workspace = true, features = ["peer-simulator"] }
sage-wallet = { workspace = true }
62 changes: 62 additions & 0 deletions crates/sage-rpc/src/cert_verifier.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
#[derive(Debug)]
pub(crate) struct WalletCertVerifier {
pub(crate) wallet_cert: Vec<u8>,
}

impl rustls::server::danger::ClientCertVerifier for WalletCertVerifier {
fn root_hint_subjects(&self) -> &[rustls::DistinguishedName] {
&[]
}

fn verify_client_cert(
&self,
end_entity: &rustls::pki_types::CertificateDer<'_>,
_intermediates: &[rustls::pki_types::CertificateDer<'_>],
_now: rustls::pki_types::UnixTime,
) -> Result<rustls::server::danger::ClientCertVerified, rustls::Error> {
if end_entity.as_ref() == self.wallet_cert {
Ok(rustls::server::danger::ClientCertVerified::assertion())
} else {
Err(rustls::Error::General(
"Client certificate not allowed".into(),
))
}
}

fn verify_tls12_signature(
&self,
message: &[u8],
cert: &rustls::pki_types::CertificateDer<'_>,
dss: &rustls::DigitallySignedStruct,
) -> Result<rustls::client::danger::HandshakeSignatureValid, rustls::Error> {
use rustls::crypto::{aws_lc_rs::default_provider, verify_tls12_signature};
verify_tls12_signature(
message,
cert,
dss,
&default_provider().signature_verification_algorithms,
)
}

fn verify_tls13_signature(
&self,
message: &[u8],
cert: &rustls::pki_types::CertificateDer<'_>,
dss: &rustls::DigitallySignedStruct,
) -> Result<rustls::client::danger::HandshakeSignatureValid, rustls::Error> {
use rustls::crypto::{aws_lc_rs::default_provider, verify_tls13_signature};
verify_tls13_signature(
message,
cert,
dss,
&default_provider().signature_verification_algorithms,
)
}

fn supported_verify_schemes(&self) -> Vec<rustls::SignatureScheme> {
use rustls::crypto::aws_lc_rs::default_provider;
default_provider()
.signature_verification_algorithms
.supported_schemes()
}
}
110 changes: 10 additions & 100 deletions crates/sage-rpc/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,9 @@
mod cert_verifier;
mod openapi;
mod rustls_config;

#[cfg(test)]
mod tests;

use std::{net::SocketAddr, sync::Arc};

Expand All @@ -10,8 +15,9 @@ use axum::{
response::{IntoResponse, Response},
routing::post,
};

use axum_server::tls_rustls::RustlsConfig;
use rustls::{ServerConfig, pki_types::PrivateKeyDer};
use rustls_config::load_rustls_config;
use sage::Sage;
use sage_api::ErrorKind;
use sage_api_macro::impl_endpoints;
Expand Down Expand Up @@ -80,7 +86,7 @@ pub async fn start_rpc(sage: Arc<Mutex<Sage>>) -> Result<()> {

drop(app);

let router = api_router().with_state(AppState { sage });
let router = make_router(sage);

axum_server::bind_rustls(addr, RustlsConfig::from_config(Arc::new(config)))
.serve(router.into_make_service())
Expand All @@ -89,102 +95,6 @@ pub async fn start_rpc(sage: Arc<Mutex<Sage>>) -> Result<()> {
Ok(())
}

fn load_rustls_config(cert_path: &str, key_path: &str) -> Result<ServerConfig> {
use anyhow::anyhow;
use std::fs;

let certs = {
let cert_file = fs::read(cert_path)?;
rustls_pemfile::certs(&mut cert_file.as_slice())
.map(|item| item.map_err(|_| anyhow!("Failed to parse certificate")))
.collect::<Result<Vec<_>>>()?
};

if certs.is_empty() {
anyhow::bail!("No certificates found in {cert_path}");
}

let mut private_keys = {
let key_file = fs::read(key_path)?;
rustls_pemfile::pkcs8_private_keys(&mut key_file.as_slice())
.map(|item| item.map_err(|_| anyhow!("Failed to parse key")))
.collect::<Result<Vec<_>>>()?
};

if private_keys.is_empty() {
anyhow::bail!("No private keys found in {key_path}");
}

let client_cert_verifier = Arc::new(WalletCertVerifier {
wallet_cert: certs[0].as_ref().to_vec(),
});

let config = ServerConfig::builder()
.with_client_cert_verifier(client_cert_verifier)
.with_single_cert(certs, PrivateKeyDer::Pkcs8(private_keys.remove(0)))?;

Ok(config)
}

#[derive(Debug)]
struct WalletCertVerifier {
wallet_cert: Vec<u8>,
}

impl rustls::server::danger::ClientCertVerifier for WalletCertVerifier {
fn root_hint_subjects(&self) -> &[rustls::DistinguishedName] {
&[]
}

fn verify_client_cert(
&self,
end_entity: &rustls::pki_types::CertificateDer<'_>,
_intermediates: &[rustls::pki_types::CertificateDer<'_>],
_now: rustls::pki_types::UnixTime,
) -> Result<rustls::server::danger::ClientCertVerified, rustls::Error> {
if end_entity.as_ref() == self.wallet_cert {
Ok(rustls::server::danger::ClientCertVerified::assertion())
} else {
Err(rustls::Error::General(
"Client certificate not allowed".into(),
))
}
}

fn verify_tls12_signature(
&self,
message: &[u8],
cert: &rustls::pki_types::CertificateDer<'_>,
dss: &rustls::DigitallySignedStruct,
) -> Result<rustls::client::danger::HandshakeSignatureValid, rustls::Error> {
use rustls::crypto::{aws_lc_rs::default_provider, verify_tls12_signature};
verify_tls12_signature(
message,
cert,
dss,
&default_provider().signature_verification_algorithms,
)
}

fn verify_tls13_signature(
&self,
message: &[u8],
cert: &rustls::pki_types::CertificateDer<'_>,
dss: &rustls::DigitallySignedStruct,
) -> Result<rustls::client::danger::HandshakeSignatureValid, rustls::Error> {
use rustls::crypto::{aws_lc_rs::default_provider, verify_tls13_signature};
verify_tls13_signature(
message,
cert,
dss,
&default_provider().signature_verification_algorithms,
)
}

fn supported_verify_schemes(&self) -> Vec<rustls::SignatureScheme> {
use rustls::crypto::aws_lc_rs::default_provider;
default_provider()
.signature_verification_algorithms
.supported_schemes()
}
pub fn make_router(sage: Arc<Mutex<Sage>>) -> Router {
api_router().with_state(AppState { sage })
}
Loading