From 022b903a29b0f10bc1a0d6d8e2c713738bb2099b Mon Sep 17 00:00:00 2001 From: Mitchell Grenier Date: Wed, 26 Jan 2022 10:50:35 -0800 Subject: [PATCH] Rustica Agent Refactor (#14) --- Cargo.lock | 8 +- examples/rustica_agent_local.toml | 4 +- examples/rustica_local_file.toml | 32 +- rustica-agent/Cargo.toml | 4 +- rustica-agent/src/config.rs | 434 ++++++++++++++++++++ rustica-agent/src/lib.rs | 120 ++++-- rustica-agent/src/main.rs | 534 +++---------------------- rustica-agent/src/rustica/cert.rs | 4 +- rustica-agent/src/rustica/key.rs | 8 +- rustica-agent/src/rustica/mod.rs | 2 +- rustica-agent/src/sshagent/handler.rs | 4 +- rustica-agent/src/sshagent/protocol.rs | 24 +- rustica/Cargo.toml | 4 +- rustica/src/auth/mod.rs | 10 +- rustica/src/server.rs | 4 +- rustica/src/signing/mod.rs | 12 +- 16 files changed, 642 insertions(+), 566 deletions(-) create mode 100644 rustica-agent/src/config.rs diff --git a/Cargo.lock b/Cargo.lock index 2f55b28..d87b21b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1889,7 +1889,7 @@ dependencies = [ [[package]] name = "rustica" -version = "0.8.0" +version = "0.8.1" dependencies = [ "aws-config", "aws-sdk-kms", @@ -1920,7 +1920,7 @@ dependencies = [ [[package]] name = "rustica-agent" -version = "0.8.0" +version = "0.8.1" dependencies = [ "base64 0.12.3", "byteorder", @@ -2233,9 +2233,9 @@ dependencies = [ [[package]] name = "sshcerts" -version = "0.9.0" +version = "0.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "703519489821e8d0c95f4616d03bff3ea0ab3499d1482d4edb83345cf5cd6255" +checksum = "1998030dbf62c4395095559c47fcbf32e1c2bea1dc2fec737a86730f0de86fc7" dependencies = [ "base64 0.13.0", "der-parser 5.1.2", diff --git a/examples/rustica_agent_local.toml b/examples/rustica_agent_local.toml index 119d1db..d3f05d7 100644 --- a/examples/rustica_agent_local.toml +++ b/examples/rustica_agent_local.toml @@ -1,5 +1,5 @@ -server = "https://localhost:50055" -socket = "/tmp/rustica_socket_dev2" +server = "https://localhost:50052" +socket = "/tmp/rustica_socket_dev3" ca_pem = ''' -----BEGIN CERTIFICATE----- MIIBJDCBzAIJAJGDT3qxW0/TMAoGCCqGSM49BAMCMBsxGTAXBgNVBAMMEEVudGVy diff --git a/examples/rustica_local_file.toml b/examples/rustica_local_file.toml index e671c90..4e126a5 100644 --- a/examples/rustica_local_file.toml +++ b/examples/rustica_local_file.toml @@ -1,37 +1,37 @@ # The certificate presented to connecting clients server_cert = ''' -----BEGIN CERTIFICATE----- -MIIBqzCCAVCgAwIBAgIJAIDetXKVHvT9MAoGCCqGSM49BAMCMBsxGTAXBgNVBAMM -EEVudGVycHJpc2VSb290Q0EwHhcNMjEwOTA5MTg0ODA1WhcNMjMxMjEzMTg0ODA1 +MIIBqjCCAVCgAwIBAgIJAOI2FtcQeixVMAoGCCqGSM49BAMCMBsxGTAXBgNVBAMM +EEVudGVycHJpc2VSb290Q0EwHhcNMjIwMTIwMDQwMjA2WhcNMjQwNDI0MDQwMjA2 WjAxMRAwDgYDVQQDDAdydXN0aWNhMRAwDgYDVQQKDAdSdXN0aWNhMQswCQYDVQQG -EwJDQTBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABA4FXg6srQVPFUNRt8O2xJU/ -lOr8sFeTfMfiXk7TS+rdEI0gk7dxtQsrNSwh+3e5UjjJEQkKbkdkhZHuHUvhS1Wj +EwJDQTBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABAINhoFW/5twPqAHLxjFjmns +lE1jJMJQXmijymZTJxR0DsNZlwvUgNH+WYQFfq4IVMwypVHgyTYJO+lAAPEeyPOj ZzBlMDUGA1UdIwQuMCyhH6QdMBsxGTAXBgNVBAMMEEVudGVycHJpc2VSb290Q0GC -CQDdGlaBb9C6TDAJBgNVHRMEAjAAMAsGA1UdDwQEAwIE8DAUBgNVHREEDTALggls -b2NhbGhvc3QwCgYIKoZIzj0EAwIDSQAwRgIhAP0YNQmo8G/LlW6lozn+JmcfT9z9 -cNI4mA6YUI2cWtOPAiEArV3Cnypaq8JBP/GCelXiojGEZHgWLaF4ZsCy+ssQjrs= +CQCRg096sVtP0zAJBgNVHRMEAjAAMAsGA1UdDwQEAwIE8DAUBgNVHREEDTALggls +b2NhbGhvc3QwCgYIKoZIzj0EAwIDSAAwRQIhAMfjW/PMrA9/cCg6O835sr22ZrNk +k/lFOODLqAJPbh3+AiAzeCUyrmxT5VTf6uyFoNT8zMoWSi79rudcdgl+32RqMg== -----END CERTIFICATE----- ''' # The key for the certificate presented to clients server_key = ''' -----BEGIN PRIVATE KEY----- -MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgv4HTe9TP3vBptisr -NEO26q5/71dv21JSpSFpwVGtQ3WhRANCAAQOBV4OrK0FTxVDUbfDtsSVP5Tq/LBX -k3zH4l5O00vq3RCNIJO3cbULKzUsIft3uVI4yREJCm5HZIWR7h1L4UtV +MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgkTd0C69xWFX9PmVf +BeD0ySfG+O0e7p7SXR9xo/enbvahRANCAAQCDYaBVv+bcD6gBy8YxY5p7JRNYyTC +UF5oo8pmUycUdA7DWZcL1IDR/lmEBX6uCFTMMqVR4Mk2CTvpQADxHsjz -----END PRIVATE KEY----- ''' # The CA certificate clients must have their identities signed by client_ca_cert = ''' -----BEGIN CERTIFICATE----- -MIIBMjCB2AIJAPWVCGGAtuF3MAoGCCqGSM49BAMCMCExHzAdBgNVBAMMFkVudGVy -cHJpc2VDbGllbnRSb290Q0EwHhcNMjEwOTA5MTg0ODA1WhcNMzEwOTA3MTg0ODA1 +MIIBMTCB2AIJAL3yJUJ7ShOJMAoGCCqGSM49BAMCMCExHzAdBgNVBAMMFkVudGVy +cHJpc2VDbGllbnRSb290Q0EwHhcNMjIwMTIwMDQwMjA2WhcNMzIwMTE4MDQwMjA2 WjAhMR8wHQYDVQQDDBZFbnRlcnByaXNlQ2xpZW50Um9vdENBMFkwEwYHKoZIzj0C -AQYIKoZIzj0DAQcDQgAEmGjwIaBON7DIrm/tSTTp96WXiovAWoUYwtziWQmqntMr -GVjQMx5DyGre2cCbMDz2AyJrEDX1ReX+mFODaP+IdTAKBggqhkjOPQQDAgNJADBG -AiEApHx6eH8yM+PrH87C/KahHCgcInFyLHvO0vVd8+sKUtICIQD8w6LSUcigT0C6 -woKz1ehFp3cKUtt2UEySnvbL/WxDSQ== +AQYIKoZIzj0DAQcDQgAEmZbAXHUQEXKB/NmHCG0AcjQA0IsBph+jmcFbw9Na58Cv +PNZtmm8jQ3Q9R8e3faG6gZuXNe60q/Ea6a6jR5UryDAKBggqhkjOPQQDAgNIADBF +AiA7H67T6QVZq1pBs6MU6+f/4mxYbVkPi/aCqMthRin7qQIhAP3cYrcO8QibwSjx +QCYQlNrEWT9OVP/akN0OyFDQIYIU -----END CERTIFICATE----- ''' diff --git a/rustica-agent/Cargo.toml b/rustica-agent/Cargo.toml index 8363981..42b1a2d 100644 --- a/rustica-agent/Cargo.toml +++ b/rustica-agent/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "rustica-agent" -version = "0.8.0" +version = "0.8.1" authors = ["Mitchell Grenier "] edition = "2018" @@ -22,7 +22,7 @@ serde = "1.0.97" serde_derive = "1.0" sha2 = "0.9.2" # For Production -sshcerts = {version = "0.9", features = ["yubikey-support"]} +sshcerts = {version = "0.9.1", features = ["yubikey-support"]} # For Development # sshcerts = {git = "https://github.com/obelisk/sshcerts", features = ["yubikey-support"]} # sshcerts = {path = "../../sshcerts", features = ["yubikey-support"]} diff --git a/rustica-agent/src/config.rs b/rustica-agent/src/config.rs new file mode 100644 index 0000000..21c620c --- /dev/null +++ b/rustica-agent/src/config.rs @@ -0,0 +1,434 @@ +use clap::{App, Arg}; + +use sshcerts::{CertType, PublicKey, PrivateKey}; +use sshcerts::yubikey::piv::{SlotId, Yubikey}; + +use rustica_agent::*; + +use std::collections::HashMap; +use std::convert::TryFrom; +use std::env; +use std::fs::{self, File}; +use std::io::{Read}; +use std::process; + +#[derive(Debug)] +pub enum ConfigurationError { + BadConfiguration, + BadSlot, + CannotAttestFileBasedKey, + CannotProvisionFile, + CannotReadFile(String), + MissingMTLSCertificate, + MissingMTLSKey, + MissingServerAddress, + MissingServerCertificateAuthority, + MissingSSHKey, + ParsingError, + YubikeyManagementKeyInvalid, + YubikeyNoKeypairFound, +} + +pub struct RunConfig { + pub socket_path: String, + pub pubkey: PublicKey, + pub handler: Handler, +} + +pub struct ProvisionConfig { + pub yubikey: YubikeySigner, + pub pin: String, + pub management_key: Vec, + pub require_touch: bool, + pub subject: String, +} + +pub struct RegisterConfig { + pub server: RusticaServer, + pub signatory: Signatory, + pub key_config: KeyConfig, +} + +pub struct ImmediateConfig { + pub server: RusticaServer, + pub certificate_options: CertificateConfig, + pub signatory: Signatory, + pub out: Option, +} + +pub enum RusticaAgentAction { + Run(RunConfig), + Immediate(ImmediateConfig), + Provision(ProvisionConfig), + Register(RegisterConfig), +} + + +impl From for ConfigurationError { + fn from(e: std::io::Error) -> Self { + ConfigurationError::CannotReadFile(e.to_string()) + } +} + +impl From for ConfigurationError { + fn from(_: sshcerts::error::Error) -> Self { + ConfigurationError::ParsingError + } +} + + +pub fn configure() -> Result { + let matches = App::new("rustica-agent") + .version(env!("CARGO_PKG_VERSION")) + .author("Mitchell Grenier ") + .about("The SSH Agent component of Rustica") + .arg( + Arg::new("config") + .help("Specify an alternate configuration file.") + .long("config") + .default_value("/etc/rustica/config.toml") + .takes_value(true), + ) + .arg( + Arg::new("server") + .help("Full address of Rustica server to use as CA") + .long("server") + .short('r') + .takes_value(true), + ) + .arg( + Arg::new("capem") + .help("Path to PEM that contains CA of the server's certificate") + .long("capem") + .short('c') + .takes_value(true), + ) + .arg( + Arg::new("mtlscert") + .help("Path to PEM that contains client cert") + .long("mtlscert") + .takes_value(true), + ) + .arg( + Arg::new("mtlskey") + .help("Path to PEM that contains client key") + .long("mtlskey") + .takes_value(true), + ) + .arg( + Arg::new("slot") + .help("Numerical value for the slot on the yubikey to use for your private key") + .long("slot") + .short('s') + .validator(slot_validator) + .takes_value(true), + ) + .arg( + Arg::new("file") + .help("Used instead of a slot to provide a private key via file") + .long("file") + .short('f') + .takes_value(true), + ) + .arg( + Arg::new("kind") + .help("The type of certificate you want to request") + .long("kind") + .short('k') + .possible_value("user") + .possible_value("host") + .takes_value(true), + ) + .arg( + Arg::new("duration") + .help("Your request for certificate duration in seconds") + .long("duration") + .short('d') + .takes_value(true), + ) + .arg( + Arg::new("principals") + .help("A comma separated list of values you are requesting as principals") + .short('n') + .takes_value(true), + ) + .arg( + Arg::new("hosts") + .help("A comma separated list of hostnames you are requesting a certificate for") + .short('h') + .takes_value(true), + ) + .arg( + Arg::new("immediate") + .help("Immiediately request a certificate. Useful for testing and verifying access") + .short('i') + ) + .arg( + Arg::new("out") + .help("Output the certificate to a file and exit. Useful for refreshing host certificates") + .short('o') + .takes_value(true) + .requires("immediate") + ) + .arg( + Arg::new("socket") + .help("Manually specify the path that will be used for the auth sock") + .long("socket") + .takes_value(true) + ) + .subcommand( + App::new("register") + .about("Take your key and register with the backend. If a hardware key, proof of providence will be sent to the backend") + .arg( + Arg::new("no-attest") + .help("Don't send an attestation even with a hardware key. Only useful if your attestation chain is broken or for testing.") + .long("no-attest") + ) + ) + .subcommand( + App::new("provision") + .about("Provision this slot with a new private key. The pin number must be passed as parameter here") + .arg( + Arg::new("management-key") + .help("Specify the management key") + .default_value("010203040506070801020304050607080102030405060708") + .long("mgmkey") + .short('m') + .required(false) + .takes_value(true), + ) + .arg( + Arg::new("pin") + .help("Specify the pin") + .default_value("123456") + .long("pin") + .short('p') + .required(false) + .takes_value(true), + ) + .arg( + Arg::new("require-touch") + .help("Newly provisioned key requires touch for signing operations (touch cached for 15 seconds)") + .long("require-touch") + .short('r') + ) + .arg( + Arg::new("subject") + .help("Subject of the new cert you're creating (this is only used as a note)") + .default_value("Rustica-AgentQuickProvision") + .long("subj") + .short('j') + ) + ) + .get_matches(); + + // Read the configuration file and use it as a base. Command line parameters + // will override values provided in the config. + let config = fs::read_to_string(matches.value_of("config").unwrap()); + let config = match config { + Ok(content) => { + if let Ok(t) = toml::from_str(&content) { + t + } else { + return Err(ConfigurationError::BadConfiguration) + } + }, + Err(_) => { + Config { + server: None, + ca_pem: None, + mtls_cert: None, + mtls_key: None, + slot: None, + key: None, + options: None, + socket: None, + } + } + }; + + let mtls_cert = match (matches.value_of("mtlscert"), &config.mtls_cert) { + (Some(mtls_cert), _) => fs::read_to_string(mtls_cert)?, + (_, Some(mtls_cert)) => mtls_cert.to_owned(), + (None, None) => return Err(ConfigurationError::MissingMTLSCertificate), + }; + + let mtls_key = match (matches.value_of("mtlskey"), &config.mtls_key) { + (Some(mtls_key), _) => fs::read_to_string(mtls_key)?, + (_, Some(mtls_key)) => mtls_key.to_owned(), + (None, None) => return Err(ConfigurationError::MissingMTLSKey), + }; + + let address = match (matches.value_of("server"), &config.server) { + (Some(server), _) => server.to_owned(), + (_, Some(server)) => server.to_owned(), + (None, None) => return Err(ConfigurationError::MissingServerAddress), + }; + + let ca = match (matches.value_of("capem"), &config.ca_pem) { + (Some(v), _) => { + let mut contents = String::new(); + File::open(v)?.read_to_string(&mut contents)?; + contents + }, + (_, Some(v)) => v.to_owned(), + (None, None) => return Err(ConfigurationError::MissingServerCertificateAuthority), + }; + + let server = RusticaServer { + address, + ca, + mtls_cert, + mtls_key, + }; + + let cmd_slot = matches.value_of("slot").map(|x| x.to_owned()); + + // Determine the signatory to be used. These match statements, execute in order, + // create a hierarchy of which keys override others. + // If a file is specified at the command line, that overrides everything else. + // If there is no file, check for a key in the config file. + // If there is no key in the config, check if a slot has been passed. + // If there is a slot both on the command line and config file, prefer the command line + // Otherwise use the slot in the config + // If none of these, error. + let mut signatory = match (&cmd_slot, &config.slot, matches.value_of("file"), &config.key) { + (Some(slot), _, _, _) => { + match slot_parser(slot) { + Some(s) => Signatory::Yubikey(YubikeySigner { + yk: Yubikey::new().unwrap(), + slot: s, + }), + None => return Err(ConfigurationError::BadSlot) + } + }, + (_, _, Some(file), _) => Signatory::Direct(PrivateKey::from_path(file)?), + (_, Some(slot), _, _) => { + match slot_parser(slot) { + Some(s) => Signatory::Yubikey(YubikeySigner { + yk: Yubikey::new().unwrap(), + slot: s, + }), + None => return Err(ConfigurationError::BadSlot), + } + }, + (_, _, _, Some(key_string)) => Signatory::Direct(PrivateKey::from_string(key_string)?), + (None, None, None, None) => return Err(ConfigurationError::MissingSSHKey) + }; + + if let Some(matches) = matches.subcommand_matches("provision") { + let yubikey = match signatory { + Signatory::Yubikey(yk_sig) => yk_sig, + Signatory::Direct(_) => return Err(ConfigurationError::CannotProvisionFile) + }; + + let require_touch = matches.is_present("require-touch"); + let subject = matches.value_of("subject").unwrap().to_string(); + let management_key = match hex::decode(matches.value_of("management-key").unwrap()) { + Ok(mgm) => mgm, + Err(_) => return Err(ConfigurationError::YubikeyManagementKeyInvalid), + }; + + let pin = matches.value_of("pin").unwrap().to_string(); + + let provision_config = ProvisionConfig { + yubikey, + pin, + management_key, + subject, + require_touch, + }; + + return Ok(RusticaAgentAction::Provision(provision_config)); + } + + let pubkey = match &mut signatory { + Signatory::Yubikey(signer) => match signer.yk.ssh_cert_fetch_pubkey(&signer.slot) { + Ok(cert) => cert, + Err(_) => return Err(ConfigurationError::YubikeyNoKeypairFound), + }, + Signatory::Direct(privkey) => privkey.pubkey.clone() + }; + + if let Some(matches) = matches.subcommand_matches("register") { + let mut key_config = KeyConfig { + certificate: vec![], + intermediate: vec![], + }; + + if !matches.is_present("no-attest") { + let signer = match &mut signatory { + Signatory::Yubikey(s) => s, + Signatory::Direct(_) => return Err(ConfigurationError::CannotAttestFileBasedKey), + }; + + key_config.certificate = signer.yk.fetch_attestation(&signer.slot).unwrap_or_default(); + key_config.intermediate = signer.yk.fetch_certificate(&SlotId::Attestation).unwrap_or_default(); + + if key_config.certificate.is_empty() || key_config.intermediate.is_empty() { + error!("Part of the attestation could not be generated. Registration may fail"); + } + } + + return Ok(RusticaAgentAction::Register(RegisterConfig { + server, + signatory, + key_config, + })) + } + + let mut certificate_options = CertificateConfig::from(config.options); + + if let Some(principals) = matches.value_of("principals") { + certificate_options.principals = principals.split(',').map(|s| s.to_string()).collect(); + } + + if let Some(hosts) = matches.value_of("hosts") { + certificate_options.hosts = hosts.split(',').map(|s| s.to_string()).collect(); + } + + if let Some(kind) = matches.value_of("kind") { + certificate_options.cert_type = CertType::try_from(kind).unwrap_or(CertType::User); + } + + if let Some(duration) = matches.value_of("duration") { + certificate_options.duration = duration.parse::().unwrap_or(10); + } + + if matches.is_present("immediate") { + let out = matches.value_of("out").map(|outfile| outfile.to_string()); + + return Ok(RusticaAgentAction::Immediate(ImmediateConfig { + server, + certificate_options, + signatory, + out, + })); + } + + let socket_path = match (matches.value_of("socket"), &config.socket) { + (Some(socket), _) => socket.to_owned(), + (_, Some(socket)) => socket.to_owned(), + (None, None) => { + let mut socket = env::temp_dir(); + socket.push(format!("rustica.{}", process::id())); + socket.to_string_lossy().to_string() + } + }; + + let handler = Handler { + server, + cert: None, + signatory, + stale_at: 0, + certificate_options, + identities: HashMap::new(), + notification_function: None, + }; + + Ok(RusticaAgentAction::Run(RunConfig { + socket_path, + pubkey, + handler, + })) +} \ No newline at end of file diff --git a/rustica-agent/src/lib.rs b/rustica-agent/src/lib.rs index 995c208..0268d89 100644 --- a/rustica-agent/src/lib.rs +++ b/rustica-agent/src/lib.rs @@ -81,7 +81,7 @@ pub struct Handler { pub stale_at: u64, pub certificate_options: CertificateConfig, pub identities: HashMap, PrivateKey>, - pub notification_function: Option () + Send + Sync>>, + pub notification_function: Option>, } impl std::fmt::Debug for Handler { @@ -213,17 +213,77 @@ impl SshAgentHandler for Handler { } } +/// Takes in a human readable slot descriptor and parses it into the Yubikey +/// slot type. +pub fn slot_parser(slot: &str) -> Option { + // If first character is R, then we need to parse the nice + // notation + if (slot.len() == 2 || slot.len() == 3) && slot.starts_with('R') { + let slot_value = slot[1..].parse::(); + match slot_value { + Ok(v) if v <= 20 => Some(SlotId::try_from(0x81_u8 + v).unwrap()), + _ => None, + } + } else if slot.len() == 4 && slot.starts_with("0x"){ + let slot_value = hex::decode(&slot[2..]).unwrap()[0]; + Some(SlotId::try_from(slot_value).unwrap()) + } else { + None + } +} + +/// Used to validate a string would parse to a valid Yubikey slot +pub fn slot_validator(slot: &str) -> Result<(), String> { + match slot_parser(slot) { + Some(_) => Ok(()), + None => Err(String::from("Provided slot was not valid. Should be R1 - R20 or a raw hex identifier")), + } +} + +/// Provisions a new keypair on the Yubikey with the given settings. +pub fn provision_new_key(mut yubikey: YubikeySigner, pin: &str, subj: &str, mgm_key: &[u8], require_touch: bool) -> Option { + println!("Provisioning new NISTP384 key in slot: {:?}", &yubikey.slot); + + let policy = if require_touch { + println!("You're creating a key that will require touch to use."); + TouchPolicy::Always + } else { + TouchPolicy::Never + }; + + if yubikey.yk.unlock(pin.as_bytes(), mgm_key).is_err() { + println!("Could not unlock key"); + return None + } + + match yubikey.yk.provision(&yubikey.slot, subj, AlgorithmId::EccP384, policy, PinPolicy::Never) { + Ok(_) => { + let certificate = yubikey.yk.fetch_attestation(&yubikey.slot); + let intermediate = yubikey.yk.fetch_certificate(&SlotId::Attestation); + + match (certificate, intermediate) { + (Ok(certificate), Ok(intermediate)) => Some(KeyConfig {certificate, intermediate}), + _ => None, + } + }, + Err(_) => panic!("Could not provision device with new key"), + } +} + /// Fetch the list of serial numbers for the connected Yubikeys /// The return from this function must be freed by the caller because we can no longer track it /// once we return +/// +/// # Safety +/// out_length must be a valid pointer to an 8 byte segment of memory #[no_mangle] -pub extern fn list_yubikeys(out_length: *mut c_int) -> *mut c_long { +pub unsafe extern fn list_yubikeys(out_length: *mut c_int) -> *mut c_long { match &mut yubikey::reader::Context::open() { Ok(readers) => { let mut serials: Vec = vec![]; for reader in readers.iter().unwrap().collect::>() { let reader = reader.open(); - if let Err(_) = reader { + if reader.is_err() { continue; } let reader = reader.unwrap(); @@ -234,9 +294,7 @@ pub extern fn list_yubikeys(out_length: *mut c_int) -> *mut c_long { let len = serials.len(); let ptr = serials.as_mut_ptr(); std::mem::forget(serials); - unsafe { - std::ptr::write(out_length, len as c_int); - } + std::ptr::write(out_length, len as c_int); ptr }, @@ -245,19 +303,26 @@ pub extern fn list_yubikeys(out_length: *mut c_int) -> *mut c_long { } /// Free the list of Yubikey Serial Numbers +/// +/// # Safety +/// This function must be passed the raw vector returned by `list_yubikeys` +/// otherwise the behaviour is undefined and will result in a crash. #[no_mangle] -pub extern fn free_list_yubikeys(length: c_int, yubikeys: *mut c_long) { +pub unsafe extern fn free_list_yubikeys(length: c_int, yubikeys: *mut c_long) { let len = length as usize; // Get back our vector. // Previously we shrank to fit, so capacity == length. - let _ = unsafe {Vec::from_raw_parts(yubikeys, len, len)}; + let _ = Vec::from_raw_parts(yubikeys, len, len); } /// The return from this function must be freed by the caller because we can no longer track it /// once we return +/// +/// # Safety +/// out_length must be a valid pointer to an 8 byte segment of memory #[no_mangle] -pub extern fn list_keys(yubikey_serial: u32, out_length: *mut c_int) -> *mut *mut c_char { +pub unsafe extern fn list_keys(yubikey_serial: u32, out_length: *mut c_int) -> *mut *mut c_char { match &mut Yubikey::open(yubikey_serial) { Ok(yk) => { let mut keys = vec![]; @@ -277,11 +342,7 @@ pub extern fn list_keys(yubikey_serial: u32, out_length: *mut c_int) -> *mut *mu let len = out.len(); let ptr = out.as_mut_ptr(); std::mem::forget(out); - - // Let's write back the length the caller can expect - unsafe { - std::ptr::write(out_length, len as c_int); - } + std::ptr::write(out_length, len as c_int); // Finally return the data ptr @@ -291,6 +352,10 @@ pub extern fn list_keys(yubikey_serial: u32, out_length: *mut c_int) -> *mut *mu } /// Free the list of Yubikey keys +/// +/// # Safety +/// This function must be passed the raw vector returned by `list_keys` +/// otherwise the behaviour is undefined and will result in a crash. #[no_mangle] pub unsafe extern fn free_list_keys(length: c_int, keys: *mut *mut c_char) { let len = length as usize; @@ -307,21 +372,25 @@ pub unsafe extern fn free_list_keys(length: c_int, keys: *mut *mut c_char) { } /// Generate and enroll a new key on the given yubikey in the given slot +/// +/// # Safety +/// Subject, config_data, and pin must all be valid, null terminated C strings +/// or this functions behaviour is undefined and will result in a crash. #[no_mangle] -pub extern fn generate_and_enroll(yubikey_serial: u32, slot: u8, high_security: bool, subject: *const c_char, config_data: *const c_char, pin: *const c_char, management_key: *const c_char) -> bool { +pub unsafe extern fn generate_and_enroll(yubikey_serial: u32, slot: u8, high_security: bool, subject: *const c_char, config_data: *const c_char, pin: *const c_char, management_key: *const c_char) -> bool { println!("Generating and enrolling a new key!"); - let cf = unsafe { CStr::from_ptr(config_data) }; + let cf = CStr::from_ptr(config_data); let config_data = match cf.to_str() { Err(_) => return false, Ok(s) => s, }; - let pin = unsafe { CStr::from_ptr(pin) }; - let management_key = unsafe { CStr::from_ptr(management_key) }; + let pin = CStr::from_ptr(pin); + let management_key = CStr::from_ptr(management_key); let management_key = hex::decode(&management_key.to_str().unwrap()).unwrap(); - let subject = unsafe { CStr::from_ptr(subject) }; + let subject = CStr::from_ptr(subject); - let config: Config = toml::from_str(&config_data).unwrap(); + let config: Config = toml::from_str(config_data).unwrap(); let alg = AlgorithmId::EccP384; let slot = SlotId::try_from(slot).unwrap(); @@ -372,21 +441,24 @@ pub extern fn generate_and_enroll(yubikey_serial: u32, slot: u8, high_security: } /// Start a new Rustica instance. Does not return unless Rustica exits. +/// # Safety +/// `config_data` and `socket_path` must be a null terminated C strings +/// or behaviour is undefined and will result in a crash. #[no_mangle] -pub extern fn start_yubikey_rustica_agent(yubikey_serial: u32, slot: u8, config_data: *const c_char, socket_path: *const c_char, notification_fn: unsafe extern "C" fn() -> ()) -> bool { +pub unsafe extern fn start_yubikey_rustica_agent(yubikey_serial: u32, slot: u8, config_data: *const c_char, socket_path: *const c_char, notification_fn: unsafe extern "C" fn() -> ()) -> bool { println!("Starting a new Rustica instance!"); let notification_f = move || { unsafe { notification_fn(); } }; - let cf = unsafe { CStr::from_ptr(config_data) }; + let cf = CStr::from_ptr(config_data); let config_data = match cf.to_str() { Err(_) => return false, Ok(s) => s, }; - let config: Config = toml::from_str(&config_data).unwrap(); + let config: Config = toml::from_str(config_data).unwrap(); let certificate_options = CertificateConfig::from(config.options); @@ -410,7 +482,7 @@ pub extern fn start_yubikey_rustica_agent(yubikey_serial: u32, slot: u8, config_ println!("Slot: {:?}", SlotId::try_from(slot)); - let sp = unsafe { CStr::from_ptr(socket_path) }; + let sp = CStr::from_ptr(socket_path); let socket_path = match sp.to_str() { Err(_) => return false, Ok(s) => s, diff --git a/rustica-agent/src/main.rs b/rustica-agent/src/main.rs index 264b3f4..0d2651d 100644 --- a/rustica-agent/src/main.rs +++ b/rustica-agent/src/main.rs @@ -1,497 +1,73 @@ #[macro_use] extern crate log; -use clap::{App, Arg}; -use rustica_agent::*; - -use std::collections::HashMap; -use std::convert::TryFrom; -use std::env; -use std::fs::{self, File}; -use std::io::{Read}; -use std::os::unix::net::{UnixListener}; -use std::process; - -use sshcerts::ssh::{Certificate, CertType, PrivateKey}; -use sshcerts::yubikey::piv::{AlgorithmId, SlotId, TouchPolicy, PinPolicy, Yubikey}; - - -fn provision_new_key(mut signatory: YubikeySigner, pin: &str, subj: &str, mgm_key: &[u8], alg: &str, secure: bool) -> Option { - let alg = match alg { - "eccp256" => AlgorithmId::EccP256, - _ => AlgorithmId::EccP384, - }; - - println!("Provisioning new {:?} key in slot: {:?}", alg, &signatory.slot); +mod config; - let policy = if secure { - println!("You're creating a secure key that will require touch to use."); - TouchPolicy::Always - } else { - TouchPolicy::Never - }; - - if signatory.yk.unlock(pin.as_bytes(), &mgm_key).is_err() { - println!("Could not unlock key"); - return None - } - - match signatory.yk.provision(&signatory.slot, subj, alg, policy, PinPolicy::Never) { - Ok(_) => { - let certificate = signatory.yk.fetch_attestation(&signatory.slot); - let intermediate = signatory.yk.fetch_certificate(&SlotId::Attestation); +use crate::config::RusticaAgentAction; +use rustica_agent::*; - match (certificate, intermediate) { - (Ok(certificate), Ok(intermediate)) => Some(KeyConfig {certificate, intermediate}), - _ => None, - } - }, - Err(_) => panic!("Could not provision device with new key"), - } -} +use sshcerts::Certificate; -fn slot_parser(slot: &str) -> Option { - // If first character is R, then we need to parse the nice - // notation - if (slot.len() == 2 || slot.len() == 3) && slot.starts_with('R') { - let slot_value = slot[1..].parse::(); - match slot_value { - Ok(v) if v <= 20 => Some(SlotId::try_from(0x81_u8 + v).unwrap()), - _ => None, - } - } else if slot.len() == 4 && slot.starts_with("0x"){ - let slot_value = hex::decode(&slot[2..]).unwrap()[0]; - Some(SlotId::try_from(slot_value).unwrap()) - } else { - None - } -} +use std::io::Write; +use std::fs::File; +use std::os::unix::net::{UnixListener}; -fn slot_validator(slot: &str) -> Result<(), String> { - match slot_parser(slot) { - Some(_) => Ok(()), - None => Err(String::from("Provided slot was not valid. Should be R1 - R20 or a raw hex identifier")), - } -} fn main() -> Result<(), Box> { env_logger::init(); - let matches = App::new("rustica-agent") - .version(env!("CARGO_PKG_VERSION")) - .author("Mitchell Grenier ") - .about("The SSH Agent component of Rustica") - .arg( - Arg::new("config") - .help("Specify an alternate configuration file.") - .long("config") - .default_value("/etc/rustica/config.toml") - .takes_value(true), - ) - .arg( - Arg::new("server") - .help("Full address of Rustica server to use as CA") - .long("server") - .short('r') - .takes_value(true), - ) - .arg( - Arg::new("capem") - .help("Path to PEM that contains CA of the server's certificate") - .long("capem") - .short('c') - .takes_value(true), - ) - .arg( - Arg::new("mtlscert") - .help("Path to PEM that contains client cert") - .long("mtlscert") - .takes_value(true), - ) - .arg( - Arg::new("mtlskey") - .help("Path to PEM that contains client key") - .long("mtlskey") - .takes_value(true), - ) - .arg( - Arg::new("slot") - .help("Numerical value for the slot on the yubikey to use for your private key") - .long("slot") - .short('s') - .validator(slot_validator) - .takes_value(true), - ) - .arg( - Arg::new("file") - .help("Used instead of a slot to provide a private key via file") - .long("file") - .short('f') - .takes_value(true), - ) - .arg( - Arg::new("kind") - .help("The type of certificate you want to request") - .long("kind") - .short('k') - .possible_value("user") - .possible_value("host") - .takes_value(true), - ) - .arg( - Arg::new("duration") - .help("Your request for certificate duration in seconds") - .long("duration") - .short('d') - .takes_value(true), - ) - .arg( - Arg::new("principals") - .help("A comma separated list of values you are requesting as principals") - .short('n') - .takes_value(true), - ) - .arg( - Arg::new("hosts") - .help("A comma separated list of hostnames you are requesting a certificate for") - .short('h') - .takes_value(true), - ) - .arg( - Arg::new("immediate") - .help("Immiediately request a certificate. Useful for testing and verifying access") - .short('i') - ) - .arg( - Arg::new("out") - .help("Output the certificate to a file and exit. Useful for refreshing host certificates") - .short('o') - .takes_value(true) - .requires("immediate") - ) - .arg( - Arg::new("socket") - .help("Manually specify the path that will be used for the auth sock") - .long("socket") - .takes_value(true) - ) - .subcommand( - App::new("register") - .about("Take your key and register with the backend. If a hardware key, proof of providence will be sent to the backend") - .arg( - Arg::new("no-attest") - .help("Don't send an attestation even with a hardware key. Only useful if your attestation chain is broken or for testing.") - .long("no-attest") - ) - ) - .subcommand( - App::new("provision") - .about("Provision this slot with a new private key. The pin number must be passed as parameter here") - .arg( - Arg::new("management-key") - .help("Specify the management key") - .default_value("010203040506070801020304050607080102030405060708") - .long("mgmkey") - .short('m') - .required(false) - .takes_value(true), - ) - .arg( - Arg::new("pin") - .help("Specify the pin") - .default_value("123456") - .long("pin") - .short('p') - .required(false) - .takes_value(true), - ) - .arg( - Arg::new("type") - .help("Specify the type of key you want to provision") - .default_value("eccp384") - .long("type") - .short('t') - .possible_value("eccp256") - .possible_value("eccp384") - .takes_value(true), - ) - .arg( - Arg::new("require-touch") - .help("Newly provisioned key requires touch for signing operations (touch cached for 15 seconds)") - .long("require-touch") - .short('r') - ) - .arg( - Arg::new("subject") - .help("Subject of the new cert you're creating (this is only used as a note)") - .default_value("Rustica-AgentQuickProvision") - .long("subj") - .short('j') - ) - ) - .get_matches(); - - // First we read the configuration file and use those unless overriden by - // the commandline - let config = fs::read_to_string(matches.value_of("config").unwrap()); - let config = match config { - Ok(content) => toml::from_str(&content)?, - Err(_) => { - Config { - server: None, - ca_pem: None, - mtls_cert: None, - mtls_key: None, - slot: None, - key: None, - options: None, - socket: None, - } - } - }; - let mut certificate_options = CertificateConfig::from(config.options); - - let mtls_cert = match (matches.value_of("mtlscert"), &config.mtls_cert) { - (Some(mtls_cert), _) => fs::read_to_string(mtls_cert)?, - (_, Some(mtls_cert)) => mtls_cert.to_owned(), - (None, None) => { - error!("You must provide an mTLS cert to present to Rustica server"); - return Err(Box::new(ConfigurationError(String::from("Missing mTLS certificate")))) - } - }; - - let mtls_key = match (matches.value_of("mtlskey"), &config.mtls_key) { - (Some(mtls_key), _) => fs::read_to_string(mtls_key)?, - (_, Some(mtls_key)) => mtls_key.to_owned(), - (None, None) => { - error!("You must provide an mTLS key to present to Rustica server"); - return Err(Box::new(ConfigurationError(String::from("Missing mTLS key")))) - } - }; - - let address = match (matches.value_of("server"), &config.server) { - (Some(server), _) => server.to_owned(), - (_, Some(server)) => server.to_owned(), - (None, None) => { - error!("A server must be specified either in the config file or on the commandline"); - return Err(Box::new(ConfigurationError(String::from("Missing server address")))) - } - }; - - let ca = match (matches.value_of("capem"), &config.ca_pem) { - (Some(v), _) => { - let mut contents = String::new(); - File::open(v)?.read_to_string(&mut contents)?; - contents - }, - (_, Some(v)) => v.to_owned(), - (None, None) => { - error!("You must provide the server certificate's issuing authority"); - return Err(Box::new(ConfigurationError(String::from("Missing server authority certificate")))) - } - }; - - let server = RusticaServer { - address, - ca, - mtls_cert, - mtls_key, - }; - - let cmd_slot = match matches.value_of("slot") { - Some(x) => Some(x.to_owned()), - None => None, - }; - - // Determine the signatory to be used. These match statements, execute in order, - // create a hierarchy of which keys override others. - // If a file is specified at the command line, that overrides everything else. - // If there is no file, check for a key in the config file. - // If there is no key in the config, check if a slot has been passed. - // If there is a slot both on the command line and config file, prefer the command line - // Otherwise use the slot in the config - // If none of these, error. - let mut signatory = match (&cmd_slot, &config.slot, matches.value_of("file"), &config.key) { - (Some(slot), _, _, _) => { - match slot_parser(slot) { - Some(s) => Signatory::Yubikey(YubikeySigner { - yk: Yubikey::new()?, - slot: s, - }), - None => { - error!("Chosen slot was invalid. Slot should be of the the form of: R# where # is between 1 and 20 inclusive"); - return Err(Box::new(ConfigurationError(String::from("Bad slot")))) - } - } - }, - (_, _, Some(file), _) => Signatory::Direct(PrivateKey::from_path(file)?), - (_, Some(slot), _, _) => { - match slot_parser(slot) { - Some(s) => Signatory::Yubikey(YubikeySigner { - yk: Yubikey::new()?, - slot: s, - }), + match config::configure() { + // Generates a new hardware backed key in a Yubikey then exits. + // This always generates a NISTP384 key. + Ok(RusticaAgentAction::Provision(config)) => { + match provision_new_key(config.yubikey, &config.pin, &config.subject, &config.management_key, config.require_touch) { + Some(_) => (), None => { - error!("Chosen slot was invalid. Slot should be of the the form of: R# where # is between 1 and 20 inclusive"); - return Err(Box::new(ConfigurationError(String::from("Bad slot")))) - } - } + println!("Provisioning Error"); + return Err(Box::new(SigningError)) + }, + }; }, - (_, _, _, Some(key_string)) => Signatory::Direct(PrivateKey::from_string(key_string)?), - (None, None, None, None) => { - error!("A slot, file, or private key must be specified for identification"); - return Err(Box::new(ConfigurationError(String::from("No identity provided")))) - } - }; - - if let Some(ref matches) = matches.subcommand_matches("provision") { - let signatory = match signatory { - Signatory::Yubikey(yk_sig) => yk_sig, - Signatory::Direct(_) => { - println!("Cannot provision a file, requires a Yubikey slot"); - return Err(Box::new(ConfigurationError(String::from("Cannot provision file")))) - } - }; - - let secure = matches.is_present("require-touch"); - let subj = matches.value_of("subject").unwrap(); - let mgm_key = match matches.value_of("management-key") { - Some(mgm) => hex::decode(mgm)?, - None => { - println!("Management key error"); - return Ok(()); - } - }; - - let pin = matches.value_of("pin").unwrap_or("123456"); - return match provision_new_key(signatory, pin, &subj, &mgm_key, matches.value_of("type").unwrap_or("eccp384"), secure) { - Some(_) => Ok(()), - None => { - println!("Provisioning Error"); - return Err(Box::new(SigningError)) - }, - } - } - - let pubkey = match &mut signatory { - Signatory::Yubikey(signer) => match signer.yk.ssh_cert_fetch_pubkey(&signer.slot) { - Ok(cert) => cert, - Err(_) => { - println!("There was no keypair found in slot {:?}. Provision one or use another slot.", &signer.slot); - return Err(Box::new(ConfigurationError(String::from("No key in slot")))) - } + Ok(RusticaAgentAction::Register(mut config)) => { + match config.server.register_key(&mut config.signatory, &config.key_config) { + Ok(_) => { + println!("Key was successfully registered"); + }, + Err(e) => { + error!("Key could not be registered. Server said: {}", e); + return Err(Box::new(e)) + }, + }; }, - Signatory::Direct(ref privkey) => privkey.pubkey.clone() - }; - - if let Some(ref matches) = matches.subcommand_matches("register") { - let mut key_config = KeyConfig { - certificate: vec![], - intermediate: vec![], - }; - - if !matches.is_present("no-attest") { - let signer = match &mut signatory { - Signatory::Yubikey(s) => s, - Signatory::Direct(_) => { - error!("You cannot attest a file based key"); - return Ok(()); + // Immediate operation: Immediately fetch a new certificate from the + // server and optionally write it to a file. This is generally used + // for debugging or in scripts where passing a certificate and key + // file is easier than using an SSH agent. + Ok(RusticaAgentAction::Immediate(mut config)) => { + match config.server.get_custom_certificate(&mut config.signatory, &config.certificate_options) { + Ok(x) => { + let cert = Certificate::from_string(&x.cert)?; + + if let Some(out_file) = config.out { + let mut out = File::create(out_file)?; + out.write_all(cert.to_string().as_bytes())?; + } else { + println!("{:#}", &cert); + } } + Err(e) => return Err(Box::new(e)), }; - key_config.certificate = signer.yk.fetch_attestation(&signer.slot).unwrap_or_default(); - key_config.intermediate = signer.yk.fetch_certificate(&SlotId::Attestation).unwrap_or_default(); - - if key_config.certificate.is_empty() || key_config.intermediate.is_empty() { - error!("Part of the attestation could not be generated. Registration may fail"); - } - } - - return match server.register_key(&mut signatory, &key_config) { - Ok(_) => { - println!("Key was successfully registered"); - Ok(()) - }, - Err(e) => { - error!("Key could not be registered. Server said: {}", e); - Err(Box::new(e)) - }, - } - } - - let mut stale_at = 0; - - if let Some(principals) = matches.value_of("principals") { - certificate_options.principals = principals.split(',').map(|s| s.to_string()).collect(); - } - - if let Some(hosts) = matches.value_of("hosts") { - certificate_options.hosts = hosts.split(',').map(|s| s.to_string()).collect(); - } - - if let Some(kind) = matches.value_of("kind") { - certificate_options.cert_type = CertType::try_from(kind).unwrap_or(CertType::User); - } - - if let Some(duration) = matches.value_of("duration") { - certificate_options.duration = duration.parse::().unwrap_or(10); - } - - let cert = if matches.is_present("immediate") { - match server.get_custom_certificate(&mut signatory, &certificate_options) { - Ok(x) => { - let cert = Certificate::from_string(&x.cert)?; - println!("Issued Certificate Details:"); - println!("{:#}\n", &cert); - stale_at = cert.valid_before; - - if let Some(out_file) = matches.value_of("out") { - use std::io::Write; - let mut out = File::create(out_file)?; - out.write_all(cert.to_string().as_bytes())?; - return Ok(()) - } - - let cert: Vec<&str> = x.cert.split(' ').collect(); - let raw_cert = base64::decode(cert[1]).unwrap_or_default(); - Some(Identity { - key_blob: raw_cert, - key_comment: x.comment, - }) - } - Err(e) => { - error!("Error: {}", e); - return Err(Box::new(e)) - }, - } - } else { - None - }; - - println!("Starting Rustica Agent"); - println!("Access Fingerprint: {}", pubkey.fingerprint().hash); - - let socket_path = match (matches.value_of("socket"), &config.socket) { - (Some(socket), _) => socket.to_owned(), - (_, Some(socket)) => socket.to_owned(), - (None, None) => { - let mut socket = env::temp_dir(); - socket.push(format!("rustica.{}", process::id())); - socket.to_string_lossy().to_string() - } - }; - - println!("SSH_AUTH_SOCK={}; export SSH_AUTH_SOCK;", socket_path); - - let handler = Handler { - server, - cert, - signatory, - stale_at, - certificate_options, - identities: HashMap::new(), - notification_function: None, + }, + // Normal operation: Starts RusticaAgent as an SSHAgent and waits to answer + // requests from SSH clients. + Ok(RusticaAgentAction::Run(config)) => { + println!("Starting Rustica Agent"); + println!("Access Fingerprint: {}", config.pubkey.fingerprint().hash); + println!("SSH_AUTH_SOCK={}; export SSH_AUTH_SOCK;", config.socket_path); + + let socket = UnixListener::bind(config.socket_path)?; + Agent::run(config.handler, socket); + }, + Err(e) => println!("Error: {:?}", e), }; - let socket = UnixListener::bind(socket_path)?; - Agent::run(handler, socket); - Ok(()) } \ No newline at end of file diff --git a/rustica-agent/src/rustica/cert.rs b/rustica-agent/src/rustica/cert.rs index fcf3b1a..ce1ff43 100644 --- a/rustica-agent/src/rustica/cert.rs +++ b/rustica-agent/src/rustica/cert.rs @@ -8,8 +8,8 @@ use std::time::SystemTime; use tokio::runtime::Runtime; impl RusticaServer { - pub async fn refresh_certificate_async(&self, mut signatory: &mut Signatory, options: &CertificateConfig) -> Result { - let (mut client, challenge) = super::complete_rustica_challenge(&self, &mut signatory).await?; + pub async fn refresh_certificate_async(&self, signatory: &mut Signatory, options: &CertificateConfig) -> Result { + let (mut client, challenge) = super::complete_rustica_challenge(self, signatory).await?; let current_timestamp = match SystemTime::now().duration_since(SystemTime::UNIX_EPOCH) { Ok(ts) => ts.as_secs(), diff --git a/rustica-agent/src/rustica/key.rs b/rustica-agent/src/rustica/key.rs index a94248e..471ce32 100644 --- a/rustica-agent/src/rustica/key.rs +++ b/rustica-agent/src/rustica/key.rs @@ -14,8 +14,8 @@ pub struct KeyConfig { } impl RusticaServer { - pub async fn register_key_async(&self, mut signatory: &mut Signatory, key: &KeyConfig) -> Result<(), RefreshError> { - let (mut client, challenge) = super::complete_rustica_challenge(&self, &mut signatory).await.unwrap(); + pub async fn register_key_async(&self, signatory: &mut Signatory, key: &KeyConfig) -> Result<(), RefreshError> { + let (mut client, challenge) = super::complete_rustica_challenge(self, signatory).await.unwrap(); let request = tonic::Request::new(RegisterKeyRequest { certificate: key.certificate.clone(), @@ -28,9 +28,9 @@ impl RusticaServer { } - pub fn register_key(&self, mut signatory: &mut Signatory, key: &KeyConfig) -> Result<(), RefreshError> { + pub fn register_key(&self, signatory: &mut Signatory, key: &KeyConfig) -> Result<(), RefreshError> { Runtime::new().unwrap().block_on(async { - self.register_key_async(&mut signatory, key).await + self.register_key_async(signatory, key).await }) } } \ No newline at end of file diff --git a/rustica-agent/src/rustica/mod.rs b/rustica-agent/src/rustica/mod.rs index 94ddfc6..98e583f 100644 --- a/rustica-agent/src/rustica/mod.rs +++ b/rustica-agent/src/rustica/mod.rs @@ -104,7 +104,7 @@ pub async fn complete_rustica_challenge(server: &RusticaServer, signatory: &mut }; let key = if key.key[0] == 0x0_u8 {&key.key[1..]} else {&key.key}; - let key_pair = signature::EcdsaKeyPair::from_private_key_and_public_key(alg, &key, &pubkey)?; + let key_pair = signature::EcdsaKeyPair::from_private_key_and_public_key(alg, key, pubkey)?; hex::encode(key_pair.sign(&rng, &decoded_challenge)?) }, diff --git a/rustica-agent/src/sshagent/handler.rs b/rustica-agent/src/sshagent/handler.rs index 19212d9..74d4110 100644 --- a/rustica-agent/src/sshagent/handler.rs +++ b/rustica-agent/src/sshagent/handler.rs @@ -12,10 +12,10 @@ pub trait SshAgentHandler: Send + Sync { fn handle_request(&mut self, request: Request) -> HandleResult { match request { - Request::RequestIdentities => { + Request::Identities => { self.identities() } - Request::SignRequest {ref pubkey_blob, ref data, ref flags} => { + Request::Sign {ref pubkey_blob, ref data, ref flags} => { self.sign_request(pubkey_blob.clone(), data.clone(), *flags) } Request::AddIdentity {private_key} => { diff --git a/rustica-agent/src/sshagent/protocol.rs b/rustica-agent/src/sshagent/protocol.rs index 2cb8f52..07b30e5 100644 --- a/rustica-agent/src/sshagent/protocol.rs +++ b/rustica-agent/src/sshagent/protocol.rs @@ -9,8 +9,8 @@ use super::error::{ParsingError, WrittingError}; #[derive(Debug)] #[derive(Copy, Clone)] enum MessageRequest { - RequestIdentities, - SignRequest, + Identities, + Sign, AddIdentity, RemoveIdentity, RemoveAllIdentities, @@ -27,8 +27,8 @@ enum MessageRequest { impl MessageRequest { fn from_u8(value: u8) -> MessageRequest { match value { - 11 => MessageRequest::RequestIdentities, - 13 => MessageRequest::SignRequest, + 11 => MessageRequest::Identities, + 13 => MessageRequest::Sign, 17 => MessageRequest::AddIdentity, 18 => MessageRequest::RemoveIdentity, 19 => MessageRequest::RemoveAllIdentities, @@ -62,8 +62,8 @@ fn write_message(w: &mut W, string: &[u8]) -> WrittingError<()> { #[derive(Debug)] pub enum Request { - RequestIdentities, - SignRequest { + Identities, + Sign { // Blob of the public key // (encoded as per RFC4253 "6.6. Public Key Algorithms"). pubkey_blob: Vec, @@ -86,11 +86,11 @@ impl Request { let msg = buf.read_u8()?; match MessageRequest::from_u8(msg) { - MessageRequest::RequestIdentities => { - Ok(Request::RequestIdentities) + MessageRequest::Identities => { + Ok(Request::Identities) } - MessageRequest::SignRequest => { - Ok(Request::SignRequest { + MessageRequest::Sign => { + Ok(Request::Sign { pubkey_blob: read_message(&mut buf)?, data: read_message(&mut buf)?, flags: buf.read_u32::()?, @@ -99,7 +99,7 @@ impl Request { MessageRequest::AddIdentity => { match sshcerts::PrivateKey::from_bytes(buf) { Ok(private_key) => { - Ok(Request::AddIdentity {private_key: private_key}) + Ok(Request::AddIdentity {private_key}) }, Err(_) => Ok(Request::Unknown) } @@ -177,7 +177,7 @@ impl Response { for identity in identities { write_message(&mut buf, &identity.key_blob)?; - write_message(&mut buf, &identity.key_comment.as_bytes())?; + write_message(&mut buf, identity.key_comment.as_bytes())?; } } Response::SignResponse { ref algo_name, ref signature } => { diff --git a/rustica/Cargo.toml b/rustica/Cargo.toml index 61df7b0..5e1ac12 100644 --- a/rustica/Cargo.toml +++ b/rustica/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "rustica" -version = "0.8.0" +version = "0.8.1" authors = ["Mitchell Grenier "] edition = "2018" @@ -29,7 +29,7 @@ ring = "0.16.20" serde = {version = "1.0", features = ["derive"]} sha2 = "0.9.2" # For Production -sshcerts = { version = "0.9", default-features = false, features = ["x509-support"] } +sshcerts = { version = "0.9.1", default-features = false, features = ["x509-support"] } # For Development # sshcerts = {git = "https://github.com/obelisk/sshcerts", branch="more_versatile_certificate_signing", default-features = false, features = ["x509-support"]} # sshcerts = {path = "../../sshcerts", features = ["x509-support"]} diff --git a/rustica/src/auth/mod.rs b/rustica/src/auth/mod.rs index 2ed1b4e..86928c8 100644 --- a/rustica/src/auth/mod.rs +++ b/rustica/src/auth/mod.rs @@ -66,16 +66,16 @@ impl AuthorizationMechanism { pub async fn authorize(&self, auth_props: &AuthorizationRequestProperties) -> Result { match &self { #[cfg(feature = "local-db")] - AuthorizationMechanism::Local(local) => local.authorize(&auth_props), - AuthorizationMechanism::External(external) => external.authorize(&auth_props).await, + AuthorizationMechanism::Local(local) => local.authorize(auth_props), + AuthorizationMechanism::External(external) => external.authorize(auth_props).await, } } pub async fn register_key(&self, register_properties: &RegisterKeyRequestProperties) -> Result { match &self { #[cfg(feature = "local-db")] - AuthorizationMechanism::Local(local) => local.register_key(®ister_properties), - AuthorizationMechanism::External(external) => external.register_key(®ister_properties).await, + AuthorizationMechanism::Local(local) => local.register_key(register_properties), + AuthorizationMechanism::External(external) => external.register_key(register_properties).await, } } @@ -95,7 +95,7 @@ impl TryInto for AuthorizationConfiguration { match (self.database, self.external) { (Some(database), None) => Ok(AuthorizationMechanism::Local(database)), (None, Some(external)) => Ok(AuthorizationMechanism::External(external)), - _ => return Err(()), + _ => Err(()), } #[cfg(not(feature = "local-db"))] diff --git a/rustica/src/server.rs b/rustica/src/server.rs index 33ad85f..f017836 100644 --- a/rustica/src/server.rs +++ b/rustica/src/server.rs @@ -228,7 +228,7 @@ impl Rustica for RusticaServer { _ => return Ok(create_response(RusticaServerError::BadRequest)), }; - let (ssh_pubkey, mtls_identities) = match validate_request(&self.hmac_key, &peer, &challenge, self.require_rustica_proof) { + let (ssh_pubkey, mtls_identities) = match validate_request(&self.hmac_key, &peer, challenge, self.require_rustica_proof) { Ok((ssh_pk, idents)) => (ssh_pk, idents), Err(e) => return Ok(create_response(e)), }; @@ -365,7 +365,7 @@ impl Rustica for RusticaServer { _ => return Err(Status::permission_denied("")), }; - let (ssh_pubkey, mtls_identities) = match validate_request(&self.hmac_key, &peer, &challenge, self.require_rustica_proof) { + let (ssh_pubkey, mtls_identities) = match validate_request(&self.hmac_key, &peer, challenge, self.require_rustica_proof) { Ok((ssh_pk, idents)) => (ssh_pk, idents), Err(e) => return Err(Status::cancelled(format!("{:?}", e))), }; diff --git a/rustica/src/signing/mod.rs b/rustica/src/signing/mod.rs index 0133963..756e431 100644 --- a/rustica/src/signing/mod.rs +++ b/rustica/src/signing/mod.rs @@ -146,18 +146,12 @@ impl SigningConfiguration { /// `SigningMechanism` enum varient. pub async fn convert_to_signing_mechanism(self) -> Result { // Try and create a file based SigningMechanism - let file_sm = match self.file { - Some(file) => Some(SigningMechanism::File(file)), - _ => None, - }; + let file_sm = self.file.map(SigningMechanism::File); // Try and create a yubikey based SigningMechanism let yubikey_sm = { #[cfg(feature = "yubikey-support")] - match self.yubikey { - Some(yubikey) => Some(SigningMechanism::Yubikey(yubikey)), - _ => None, - } + {self.yubikey.map(SigningMechanism::Yubikey)} #[cfg(not(feature = "yubikey-support"))] None }; @@ -186,7 +180,7 @@ impl SigningConfiguration { (Some(file), None, None) => Ok(file), (None, Some(yubikey), None) => Ok(yubikey), (None, None, Some(amazonkms)) => Ok(amazonkms), - _ => return Err(()), + _ => Err(()), } } }