From ae5aae7fb48376596d3d0e07a4e41ea97e31f9a4 Mon Sep 17 00:00:00 2001 From: Mark Poliakov Date: Fri, 1 Nov 2024 15:32:19 +0200 Subject: [PATCH] user: add a basic cryptography tool --- userspace/Cargo.lock | 227 +++++++++++++++++++++++++------ userspace/Cargo.toml | 12 +- userspace/crypt/Cargo.toml | 12 ++ userspace/crypt/src/lib.rs | 113 +++++++++++++++ userspace/crypt/src/main.rs | 122 +++++++++++++++++ userspace/crypt/src/rsa/mod.rs | 126 +++++++++++++++++ userspace/lib/yasync/Cargo.toml | 9 ++ userspace/lib/yasync/src/lib.rs | 219 +++++++++++++++++++++++++++++ userspace/lib/yasync/src/main.rs | 23 ++++ userspace/rsh/Cargo.toml | 1 - xtask/src/build/userspace.rs | 2 + 11 files changed, 824 insertions(+), 42 deletions(-) create mode 100644 userspace/crypt/Cargo.toml create mode 100644 userspace/crypt/src/lib.rs create mode 100644 userspace/crypt/src/main.rs create mode 100644 userspace/crypt/src/rsa/mod.rs create mode 100644 userspace/lib/yasync/Cargo.toml create mode 100644 userspace/lib/yasync/src/lib.rs create mode 100644 userspace/lib/yasync/src/main.rs diff --git a/userspace/Cargo.lock b/userspace/Cargo.lock index 58b501ab..bf229970 100644 --- a/userspace/Cargo.lock +++ b/userspace/Cargo.lock @@ -8,7 +8,7 @@ version = "0.1.0" dependencies = [ "proc-macro2", "quote", - "syn 2.0.85", + "syn 2.0.86", "thiserror", ] @@ -78,7 +78,7 @@ checksum = "bcfcc3cd946cb52f0bbfdbbcfa2f4e24f75ebb6c0e1002f7c25904fada18b9ec" dependencies = [ "proc-macro2", "quote", - "syn 2.0.85", + "syn 2.0.86", ] [[package]] @@ -140,7 +140,7 @@ dependencies = [ "heck", "proc-macro2", "quote", - "syn 2.0.85", + "syn 2.0.86", ] [[package]] @@ -209,6 +209,17 @@ dependencies = [ "winapi", ] +[[package]] +name = "crypt" +version = "0.1.0" +dependencies = [ + "clap", + "ed25519-dalek", + "rand 0.8.5", + "rsa", + "thiserror", +] + [[package]] name = "crypto-common" version = "0.1.6" @@ -222,8 +233,7 @@ dependencies = [ [[package]] name = "curve25519-dalek" version = "4.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "97fb8b7c4503de7d6ae7b42ab72a5a59857b4c937ec27a3d4539dba95b5ab2be" +source = "git+https://git.alnyan.me/yggdrasil/curve25519-dalek.git?branch=alnyan%2Fyggdrasil#5f4dbb09259347077d3a5024ba60c77efde93a3a" dependencies = [ "cfg-if", "cpufeatures", @@ -238,12 +248,11 @@ dependencies = [ [[package]] name = "curve25519-dalek-derive" version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f46882e17999c6cc590af592290432be3bce0428cb0d5f8b6715e4dc7b383eb3" +source = "git+https://git.alnyan.me/yggdrasil/curve25519-dalek.git?branch=alnyan%2Fyggdrasil#5f4dbb09259347077d3a5024ba60c77efde93a3a" dependencies = [ "proc-macro2", "quote", - "syn 2.0.85", + "syn 2.0.86", ] [[package]] @@ -253,6 +262,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f55bf8e7b65898637379c1b74eb1551107c8294ed26d855ceb9fd1a09cfc9bc0" dependencies = [ "const-oid", + "pem-rfc7468", "zeroize", ] @@ -272,6 +282,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" dependencies = [ "block-buffer", + "const-oid", "crypto-common", ] @@ -282,17 +293,17 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "115531babc129696a58c64a4fef0a8bf9e9698629fb97e9e40767d235cfbcd53" dependencies = [ "pkcs8", - "signature", + "signature 2.2.0 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "ed25519-dalek" version = "2.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4a3daa8e81a3963a60642bcc1f90a670680bd4a77535faa384e9d1c79d620871" +source = "git+https://git.alnyan.me/yggdrasil/curve25519-dalek.git?branch=alnyan%2Fyggdrasil#5f4dbb09259347077d3a5024ba60c77efde93a3a" dependencies = [ "curve25519-dalek", "ed25519", + "rand_core 0.6.4 (git+https://git.alnyan.me/yggdrasil/rand.git?branch=alnyan%2Fyggdrasil-rng_core-0.6.4)", "serde", "sha2", "subtle", @@ -387,7 +398,7 @@ checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" dependencies = [ "proc-macro2", "quote", - "syn 2.0.85", + "syn 2.0.86", ] [[package]] @@ -522,6 +533,9 @@ name = "lazy_static" version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" +dependencies = [ + "spin", +] [[package]] name = "libc" @@ -624,7 +638,7 @@ dependencies = [ "bytemuck", "clap", "clap-num", - "rand", + "rand 0.9.0-alpha.1", "serde", "serde_json", "thiserror", @@ -642,12 +656,48 @@ dependencies = [ "minimal-lexical", ] +[[package]] +name = "num-bigint-dig" +version = "0.8.2" +source = "git+https://git.alnyan.me/yggdrasil/num-bigint-dig.git?branch=alnyan%2Fyggdrasil-0.8.2#e0ff3fcf8e9f5612be8ede9df7458bd762ac525f" +dependencies = [ + "byteorder", + "lazy_static", + "libm", + "num-integer", + "num-iter", + "num-traits", + "rand 0.8.5", + "smallvec", + "zeroize", +] + [[package]] name = "num-conv" version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9" +[[package]] +name = "num-integer" +version = "0.1.46" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f" +dependencies = [ + "num-traits", +] + +[[package]] +name = "num-iter" +version = "0.1.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1429034a0490724d0075ebb2bc9e875d6503c3cf69e235a8941aa757d83ef5bf" +dependencies = [ + "autocfg", + "num-integer", + "num-traits", +] + [[package]] name = "num-traits" version = "0.2.19" @@ -717,6 +767,15 @@ dependencies = [ "windows-targets 0.52.6", ] +[[package]] +name = "pem-rfc7468" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88b39c9bfcfc231068454382784bb460aae594343fb030d46e9f50a645418412" +dependencies = [ + "base64ct", +] + [[package]] name = "percent-encoding" version = "2.3.1" @@ -735,6 +794,17 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" +[[package]] +name = "pkcs1" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8ffb9f10fa047879315e6625af03c164b16962a5368d724ed16323b68ace47f" +dependencies = [ + "der", + "pkcs8", + "spki", +] + [[package]] name = "pkcs8" version = "0.10.2" @@ -757,7 +827,7 @@ version = "0.2.20" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "77957b295656769bb8ad2b6a6b09d897d94f05c41b069aede1fcdaa675eaea04" dependencies = [ - "zerocopy", + "zerocopy 0.7.35", ] [[package]] @@ -767,7 +837,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "64d1ec885c64d0457d564db4ec299b2dae3f9c02808b8ad9c3a089c591b18033" dependencies = [ "proc-macro2", - "syn 2.0.85", + "syn 2.0.86", ] [[package]] @@ -800,22 +870,40 @@ dependencies = [ [[package]] name = "rand" -version = "0.9.0" -source = "git+https://git.alnyan.me/yggdrasil/rand.git?branch=alnyan%2Fyggdrasil#9fac921fcc405a936b9a88b6bb9f2ced264bc0ec" +version = "0.8.5" +source = "git+https://git.alnyan.me/yggdrasil/rand.git?branch=alnyan%2Fyggdrasil-rng_core-0.6.4#27d8b41f68e239bf5b2e5079554f1b861966672f" dependencies = [ "libc", - "rand_chacha", - "rand_core 0.7.0", - "zerocopy", + "rand_chacha 0.3.1", + "rand_core 0.6.4 (git+https://git.alnyan.me/yggdrasil/rand.git?branch=alnyan%2Fyggdrasil-rng_core-0.6.4)", +] + +[[package]] +name = "rand" +version = "0.9.0-alpha.1" +source = "git+https://git.alnyan.me/yggdrasil/rand.git?branch=alnyan%2Fyggdrasil#d78efe056f23f1896b0e4f03a62d4b6adc37ea07" +dependencies = [ + "rand_chacha 0.9.0-alpha.1", + "rand_core 0.9.0-alpha.1", + "zerocopy 0.8.8", ] [[package]] name = "rand_chacha" -version = "0.4.0" -source = "git+https://git.alnyan.me/yggdrasil/rand.git?branch=alnyan%2Fyggdrasil#9fac921fcc405a936b9a88b6bb9f2ced264bc0ec" +version = "0.3.1" +source = "git+https://git.alnyan.me/yggdrasil/rand.git?branch=alnyan%2Fyggdrasil-rng_core-0.6.4#27d8b41f68e239bf5b2e5079554f1b861966672f" dependencies = [ "ppv-lite86", - "rand_core 0.7.0", + "rand_core 0.6.4 (git+https://git.alnyan.me/yggdrasil/rand.git?branch=alnyan%2Fyggdrasil-rng_core-0.6.4)", +] + +[[package]] +name = "rand_chacha" +version = "0.9.0-alpha.1" +source = "git+https://git.alnyan.me/yggdrasil/rand.git?branch=alnyan%2Fyggdrasil#d78efe056f23f1896b0e4f03a62d4b6adc37ea07" +dependencies = [ + "ppv-lite86", + "rand_core 0.9.0-alpha.1", ] [[package]] @@ -829,11 +917,19 @@ dependencies = [ [[package]] name = "rand_core" -version = "0.7.0" -source = "git+https://git.alnyan.me/yggdrasil/rand.git?branch=alnyan%2Fyggdrasil#9fac921fcc405a936b9a88b6bb9f2ced264bc0ec" +version = "0.6.4" +source = "git+https://git.alnyan.me/yggdrasil/rand.git?branch=alnyan%2Fyggdrasil-rng_core-0.6.4#27d8b41f68e239bf5b2e5079554f1b861966672f" dependencies = [ "getrandom 0.2.12", - "zerocopy", +] + +[[package]] +name = "rand_core" +version = "0.9.0-alpha.1" +source = "git+https://git.alnyan.me/yggdrasil/rand.git?branch=alnyan%2Fyggdrasil#d78efe056f23f1896b0e4f03a62d4b6adc37ea07" +dependencies = [ + "getrandom 0.2.12", + "zerocopy 0.8.8", ] [[package]] @@ -892,6 +988,25 @@ dependencies = [ "bitflags 2.6.0", ] +[[package]] +name = "rsa" +version = "0.9.6" +source = "git+https://git.alnyan.me/yggdrasil/rsa.git?branch=alnyan%2Fyggdrasil#1ebb9403b0ad1ca7eab5dffbb0837a53d68c4ca3" +dependencies = [ + "const-oid", + "digest", + "num-bigint-dig", + "num-integer", + "num-traits", + "pkcs1", + "pkcs8", + "rand_core 0.6.4 (git+https://git.alnyan.me/yggdrasil/rand.git?branch=alnyan%2Fyggdrasil-rng_core-0.6.4)", + "signature 2.2.0 (git+https://git.alnyan.me/yggdrasil/rustcrypto-traits.git?branch=alnyan%2Fyggdrasil-2.2.0)", + "spki", + "subtle", + "zeroize", +] + [[package]] name = "rsh" version = "0.1.0" @@ -899,7 +1014,6 @@ dependencies = [ "bytemuck", "clap", "cross", - "ed25519-dalek", "flexbuffers", "libterm", "serde", @@ -980,7 +1094,7 @@ checksum = "de523f781f095e28fa605cdce0f8307e451cc0fd14e2eb4cd2e98a355b147766" dependencies = [ "proc-macro2", "quote", - "syn 2.0.85", + "syn 2.0.86", ] [[package]] @@ -1060,7 +1174,16 @@ version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "77549399552de45a898a580c1b41d445bf730df867cc44e6c0233bbc4b8329de" dependencies = [ - "rand_core 0.6.4", + "rand_core 0.6.4 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "signature" +version = "2.2.0" +source = "git+https://git.alnyan.me/yggdrasil/rustcrypto-traits.git?branch=alnyan%2Fyggdrasil-2.2.0#ac3771039858e20b812450ff7b6bf67e29436208" +dependencies = [ + "digest", + "rand_core 0.6.4 (git+https://git.alnyan.me/yggdrasil/rand.git?branch=alnyan%2Fyggdrasil-rng_core-0.6.4)", ] [[package]] @@ -1081,6 +1204,12 @@ dependencies = [ "serde", ] +[[package]] +name = "spin" +version = "0.9.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" + [[package]] name = "spki" version = "0.7.3" @@ -1122,9 +1251,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.85" +version = "2.0.86" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5023162dfcd14ef8f32034d8bcd4cc5ddc61ef7a247c024a33e24e1f24d21b56" +checksum = "e89275301d38033efb81a6e60e3497e734dfcc62571f2854bf4b16690398824c" dependencies = [ "proc-macro2", "quote", @@ -1152,7 +1281,7 @@ dependencies = [ "humansize", "init", "libterm", - "rand", + "rand 0.9.0-alpha.1", "serde", "serde_json", "sha2", @@ -1186,22 +1315,22 @@ dependencies = [ [[package]] name = "thiserror" -version = "1.0.65" +version = "1.0.66" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5d11abd9594d9b38965ef50805c5e469ca9cc6f197f883f717e0269a3057b3d5" +checksum = "5d171f59dbaa811dbbb1aee1e73db92ec2b122911a48e1390dfe327a821ddede" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.65" +version = "1.0.66" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ae71770322cbd277e69d762a16c444af02aa0575ac0d174f0b9562d3b37f8602" +checksum = "b08be0f17bd307950653ce45db00cd31200d82b624b36e181337d9c7d92765b5" dependencies = [ "proc-macro2", "quote", - "syn 2.0.85", + "syn 2.0.86", ] [[package]] @@ -1548,7 +1677,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1b9b4fd18abc82b8136838da5d50bae7bdea537c574d8dc1a34ed098d6c166f0" dependencies = [ "byteorder", - "zerocopy-derive", + "zerocopy-derive 0.7.35", +] + +[[package]] +name = "zerocopy" +version = "0.8.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a4e33e6dce36f2adba29746927f8e848ba70989fdb61c772773bbdda8b5d6a7" +dependencies = [ + "zerocopy-derive 0.8.8", ] [[package]] @@ -1559,7 +1697,18 @@ checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e" dependencies = [ "proc-macro2", "quote", - "syn 2.0.85", + "syn 2.0.86", +] + +[[package]] +name = "zerocopy-derive" +version = "0.8.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3cd137b4cc21bde6ecce3bbbb3350130872cda0be2c6888874279ea76e17d4c1" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.86", ] [[package]] diff --git a/userspace/Cargo.toml b/userspace/Cargo.toml index 751978cd..ff219af2 100644 --- a/userspace/Cargo.toml +++ b/userspace/Cargo.toml @@ -16,7 +16,8 @@ members = [ "rdb", "lib/yasync", "rsh", - "lib/cross" + "lib/cross", + "crypt" ] exclude = ["dynload-program", "test-kernel-module"] @@ -27,9 +28,16 @@ serde_json = "1.0.132" serde = { version = "1.0.214", features = ["derive"] } bytemuck = "1.19.0" thiserror = "1.0.64" -rand = { git = "https://git.alnyan.me/yggdrasil/rand.git", branch = "alnyan/yggdrasil" } flexbuffers = "2.0.0" +# Vendored/patched dependencies +rand = { git = "https://git.alnyan.me/yggdrasil/rand.git", branch = "alnyan/yggdrasil" } +ring = { git = "https://git.alnyan.me/yggdrasil/ring.git", branch = "alnyan/yggdrasil" } +rsa = { git = "https://git.alnyan.me/yggdrasil/rsa.git", branch = "alnyan/yggdrasil" } +curve25519-dalek = { git = "https://git.alnyan.me/yggdrasil/curve25519-dalek.git", branch = "alnyan/yggdrasil" } +x25519-dalek = { git = "https://git.alnyan.me/yggdrasil/curve25519-dalek.git", branch = "alnyan/yggdrasil" } +ed25519-dalek = { git = "https://git.alnyan.me/yggdrasil/curve25519-dalek.git", branch = "alnyan/yggdrasil" } + # Internal crates serde-ipc.path = "lib/serde-ipc" libterm.path = "lib/libterm" diff --git a/userspace/crypt/Cargo.toml b/userspace/crypt/Cargo.toml new file mode 100644 index 00000000..3df1dde6 --- /dev/null +++ b/userspace/crypt/Cargo.toml @@ -0,0 +1,12 @@ +[package] +name = "crypt" +version = "0.1.0" +edition = "2021" + +[dependencies] +rsa.workspace = true +ed25519-dalek = { workspace = true, features = ["rand_core", "pem"] } +clap.workspace = true +thiserror.workspace = true + +rand = { git = "https://git.alnyan.me/yggdrasil/rand.git", branch = "alnyan/yggdrasil-rng_core-0.6.4" } diff --git a/userspace/crypt/src/lib.rs b/userspace/crypt/src/lib.rs new file mode 100644 index 00000000..a5f81181 --- /dev/null +++ b/userspace/crypt/src/lib.rs @@ -0,0 +1,113 @@ +use std::{ + fs::File, + io::{self, stdin, stdout, BufRead, BufReader, BufWriter, Read, Stdin, Stdout, Write}, + path::{Path, PathBuf}, +}; + +pub mod rsa; + +#[derive(Debug, thiserror::Error)] +pub enum Error { + #[error("{0}")] + Io(#[from] io::Error), + #[error("RSA error: {0}")] + Rsa(#[from] ::rsa::Error), + #[error("PKCS#1 error: {0}")] + Pkcs1(#[from] ::rsa::pkcs1::Error), + #[error("PKCS#8 error: {0}")] + Pkcs8(#[from] ::rsa::pkcs8::Error), + #[error("PKCS#8 error: {0}")] + Pkcs8Spki(#[from] ::rsa::pkcs8::spki::Error), + #[error("incompatible options: {0}")] + IncompatibleOptions(String), +} + +#[derive(Debug, clap::Parser)] +pub struct AsymmetricEncryptOptions { + #[clap(short, long, help = "Ciphertext output (default: stdout)")] + pub output: Option, + #[clap(short = 'k', long, help = "Public key to use")] + pub public_key: PathBuf, + #[clap(help = "Plaintext input (default: stdin)")] + pub input: Option, +} + +#[derive(Debug, clap::Parser)] +pub struct AsymmetricDecryptOptions { + #[clap(short, long, help = "Plaintext output (default: stdout)")] + pub output: Option, + #[clap(short = 'k', long, help = "Private key to use")] + pub private_key: PathBuf, + #[clap(help = "Ciphertext input (default: stdin)")] + pub input: Option, +} + +#[derive(Debug, clap::Parser)] +pub struct AsymmetricKeyOptions { + #[clap(short = 'o', long, help = "Public key output file (default: stdout)")] + pub public_key: Option, + #[clap(help = "Private key output file")] + pub private_key: PathBuf, +} + +#[derive(Debug)] +pub enum Input { + Stdin(Stdin), + File(BufReader), +} + +#[derive(Debug)] +pub enum Output { + Stdout(Stdout), + File(BufWriter), +} + +impl Input { + pub fn from_path_option>(option: Option

) -> Result { + match option { + Some(path) => File::open(path).map(BufReader::new).map(Self::File), + None => Ok(Self::Stdin(stdin())), + } + } + + pub fn read_line(&mut self, buf: &mut String) -> io::Result { + match self { + Self::Stdin(stdin) => stdin.read_line(buf), + Self::File(file) => file.read_line(buf), + } + } +} + +impl Read for Input { + fn read(&mut self, buf: &mut [u8]) -> io::Result { + match self { + Self::Stdin(stdin) => stdin.read(buf), + Self::File(file) => file.read(buf), + } + } +} + +impl Output { + pub fn from_path_option>(option: Option

) -> Result { + match option { + Some(path) => File::create(path).map(BufWriter::new).map(Self::File), + None => Ok(Self::Stdout(stdout())), + } + } +} + +impl Write for Output { + fn write(&mut self, buf: &[u8]) -> io::Result { + match self { + Self::Stdout(stdout) => stdout.write(buf), + Self::File(file) => file.write(buf), + } + } + + fn flush(&mut self) -> io::Result<()> { + match self { + Self::Stdout(stdout) => stdout.flush(), + Self::File(file) => file.flush(), + } + } +} diff --git a/userspace/crypt/src/main.rs b/userspace/crypt/src/main.rs new file mode 100644 index 00000000..1f84d22c --- /dev/null +++ b/userspace/crypt/src/main.rs @@ -0,0 +1,122 @@ +use std::process::ExitCode; + +use clap::Parser; +use crypt::{ + rsa::RsaKeyOptions, AsymmetricDecryptOptions, AsymmetricEncryptOptions, AsymmetricKeyOptions, + Error, +}; +use rsa::pkcs8::{EncodePrivateKey, EncodePublicKey, LineEnding}; + +#[derive(Parser, Debug)] +struct Args { + #[clap(subcommand)] + command: Command, +} + +#[derive(Debug, clap::Subcommand)] +enum Command { + #[clap(subcommand, about = "Generate a key/keypair")] + Generate(GenerateCommand), + #[clap(subcommand, about = "Encrypt data using a public key")] + Encrypt(EncryptCommand), + #[clap(subcommand, about = "Decrypt data using a private key")] + Decrypt(DecryptCommand), +} + +#[derive(Debug, clap::Subcommand)] +enum GenerateCommand { + Rsa { + #[clap(short = 'b', long, help = "Key bitness")] + bits: usize, + #[clap(flatten)] + options: AsymmetricKeyOptions, + #[clap(flatten)] + rsa_options: RsaKeyOptions, + }, + Ed25519 { + #[clap(flatten)] + options: AsymmetricKeyOptions, + }, +} + +#[derive(Debug, clap::Subcommand)] +enum EncryptCommand { + Rsa { + #[clap(flatten)] + options: AsymmetricEncryptOptions, + }, +} + +#[derive(Debug, clap::Subcommand)] +enum DecryptCommand { + Rsa { + #[clap(flatten)] + options: AsymmetricDecryptOptions, + }, +} + +fn generate_ed25519(options: &AsymmetricKeyOptions) -> Result<(), Error> { + let mut rng = rand::thread_rng(); + let signing_key = ed25519_dalek::SigningKey::generate(&mut rng); + + println!("Generating a new ed25519 keypair"); + signing_key.write_pkcs8_pem_file(&options.private_key, LineEnding::LF)?; + println!("Done, deriving a verifying key"); + + if let Some(public_key) = options.public_key.as_ref() { + signing_key + .verifying_key() + .write_public_key_pem_file(public_key, LineEnding::LF)?; + } else { + let key_string = signing_key + .verifying_key() + .to_public_key_pem(LineEnding::LF)?; + println!("Public key:"); + println!("{key_string}"); + } + println!("Done!"); + Ok(()) +} + +fn generate(command: GenerateCommand) -> Result<(), Error> { + match command { + GenerateCommand::Rsa { + options, + rsa_options, + bits, + } => crypt::rsa::generate_rsa(&options, &rsa_options, bits), + GenerateCommand::Ed25519 { options } => generate_ed25519(&options), + } +} + +fn encrypt(command: EncryptCommand) -> Result<(), Error> { + match command { + EncryptCommand::Rsa { options } => crypt::rsa::encrypt_rsa(&options), + } +} + +fn decrypt(command: DecryptCommand) -> Result<(), Error> { + match command { + DecryptCommand::Rsa { options } => crypt::rsa::decrypt_rsa(&options), + } +} + +fn run(command: Command) -> Result<(), Error> { + match command { + Command::Generate(command) => generate(command), + Command::Encrypt(command) => encrypt(command), + Command::Decrypt(command) => decrypt(command), + } +} + +fn main() -> ExitCode { + let args = Args::parse(); + + match run(args.command) { + Ok(_) => ExitCode::SUCCESS, + Err(error) => { + eprintln!("Error: {error}"); + ExitCode::FAILURE + } + } +} diff --git a/userspace/crypt/src/rsa/mod.rs b/userspace/crypt/src/rsa/mod.rs new file mode 100644 index 00000000..b4da109e --- /dev/null +++ b/userspace/crypt/src/rsa/mod.rs @@ -0,0 +1,126 @@ +use std::{ + io::{Read, Write}, + path::Path, +}; + +use rsa::{ + pkcs1::{DecodeRsaPrivateKey, DecodeRsaPublicKey, EncodeRsaPrivateKey, EncodeRsaPublicKey}, + pkcs8::LineEnding, + Pkcs1v15Encrypt, RsaPrivateKey, RsaPublicKey, +}; + +use crate::{ + AsymmetricDecryptOptions, AsymmetricEncryptOptions, AsymmetricKeyOptions, Error, Input, Output, +}; + +#[derive(Default, Debug, Clone, Copy, PartialEq, clap::ValueEnum)] +pub enum KeyFormat { + #[default] + #[clap(alias("pem"), help = "PKCS#1 PEM")] + Pkcs1Pem, + #[clap(alias("der"), help = "PKCS#1 DER")] + Pkcs1Der, +} + +#[derive(Debug, clap::Parser)] +pub struct RsaKeyOptions { + #[clap(short = 'f', long, help = "Key output format", default_value = "pem")] + pub format: KeyFormat, +} + +pub fn read_private_key>(keyfile: P) -> Result { + let keyfile = keyfile.as_ref(); + if let Ok(key) = RsaPrivateKey::read_pkcs1_pem_file(keyfile) { + return Ok(key); + } + let key = RsaPrivateKey::read_pkcs1_der_file(keyfile)?; + Ok(key) +} + +pub fn read_public_key>(keyfile: P) -> Result { + let keyfile = keyfile.as_ref(); + if let Ok(key) = RsaPublicKey::read_pkcs1_pem_file(keyfile) { + return Ok(key); + } + let key = RsaPublicKey::read_pkcs1_der_file(keyfile)?; + Ok(key) +} + +pub fn generate_rsa( + options: &AsymmetricKeyOptions, + rsa: &RsaKeyOptions, + bits: usize, +) -> Result<(), Error> { + if rsa.format == KeyFormat::Pkcs1Der && options.public_key.is_none() { + return Err(Error::IncompatibleOptions(format!( + "DER output format requires -o/--public-key" + ))); + } + + println!("Generating a new RSA keypair, bits={}", bits); + println!("This will take a while"); + let mut rng = rand::thread_rng(); + let private_key = RsaPrivateKey::new(&mut rng, bits)?; + + println!("Done, deriving a public key"); + let public_key = private_key.to_public_key(); + + match rsa.format { + KeyFormat::Pkcs1Pem => { + private_key.write_pkcs1_pem_file(&options.private_key, LineEnding::LF)?; + + if let Some(public_key_path) = options.public_key.as_ref() { + public_key.write_pkcs1_pem_file(public_key_path, LineEnding::LF)?; + } else { + let output = public_key.to_pkcs1_pem(LineEnding::LF)?; + println!("Public key:"); + println!("{output}"); + } + } + KeyFormat::Pkcs1Der => { + private_key.write_pkcs1_der_file(&options.private_key)?; + + if let Some(public_key_path) = options.public_key.as_ref() { + public_key.write_pkcs1_der_file(public_key_path)?; + } + } + } + + println!("Done!"); + Ok(()) +} + +pub fn encrypt_rsa(options: &AsymmetricEncryptOptions) -> Result<(), Error> { + let key = read_public_key(&options.public_key)?; + let mut input = Input::from_path_option(options.input.as_ref())?; + let mut output = Output::from_path_option(options.output.as_ref())?; + let mut buf = [0; 2048]; + let mut rng = rand::thread_rng(); + loop { + let len = input.read(&mut buf)?; + if len == 0 { + break; + } + let ciphertext = key.encrypt(&mut rng, Pkcs1v15Encrypt, &buf[..len])?; + output.write_all(&ciphertext)?; + } + + Ok(()) +} + +pub fn decrypt_rsa(options: &AsymmetricDecryptOptions) -> Result<(), Error> { + let key = read_private_key(&options.private_key)?; + let mut input = Input::from_path_option(options.input.as_ref())?; + let mut output = Output::from_path_option(options.output.as_ref())?; + let mut buf = [0; 2048]; + loop { + let len = input.read(&mut buf)?; + if len == 0 { + break; + } + let plaintext = key.decrypt(Pkcs1v15Encrypt, &buf[..len])?; + output.write_all(&plaintext)?; + } + + Ok(()) +} diff --git a/userspace/lib/yasync/Cargo.toml b/userspace/lib/yasync/Cargo.toml new file mode 100644 index 00000000..5fdbdea0 --- /dev/null +++ b/userspace/lib/yasync/Cargo.toml @@ -0,0 +1,9 @@ +[package] +name = "yasync" +version = "0.1.0" +edition = "2021" + +[dependencies] +cross.workspace = true +futures-util = "0.3.31" +spmc = "0.3.0" diff --git a/userspace/lib/yasync/src/lib.rs b/userspace/lib/yasync/src/lib.rs new file mode 100644 index 00000000..5d2fc463 --- /dev/null +++ b/userspace/lib/yasync/src/lib.rs @@ -0,0 +1,219 @@ +#![cfg_attr(target_os = "yggdrasil", feature(yggdrasil_os, rustc_private))] +use std::{ + cell::OnceCell, + collections::HashMap, + future::Future, + os::fd::{AsRawFd, RawFd}, + pin::Pin, + sync::{Arc, Mutex}, + task::{Context, Poll, Waker}, + thread::JoinHandle, + time::Duration, +}; + +use cross::io::{Poll as PollChannel, TimerFd}; +use futures_util::task::{waker_ref, ArcWake}; +use spmc::{Receiver, Sender}; + +pub type BoxFuture<'a, T> = Pin + Send + 'a>>; + +pub struct Executor { + _id: usize, + queue: Arc>>, +} + +pub struct Task { + future: Mutex>>, + runtime: Arc, +} + +struct ReactorInner { + poll: PollChannel, + event_map: HashMap, +} + +pub struct Reactor { + // TODO this lock will become very contended + inner: Mutex, +} + +struct RuntimeInner { + // TODO this lock will become very contended + sender: Mutex>>, + reactor: Arc +} + +pub struct Runtime { + inner: Arc, + executors: Vec>, + reactor: JoinHandle<()>, +} + +// TODO use global timer instead of creating a timer for each sleep +pub struct Sleep { + timer: TimerFd, +} + +impl Future for Sleep { + type Output = (); + + fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { + let expired = self.timer.is_expired().unwrap(); + if expired { + return Poll::Ready(()); + } + let reactor = &RuntimeInner::get().reactor; + reactor.register(&self.timer, cx.waker().clone()); + Poll::Pending + } +} + +impl Executor { + pub fn new(id: usize, queue: Arc>>) -> Self { + Self { _id: id, queue } + } + + pub fn run(self) { + loop { + let task = self.queue.recv().unwrap(); + let mut slot = task.future.lock().unwrap(); + + if let Some(mut future) = slot.take() { + let waker = waker_ref(&task); + let context = &mut Context::from_waker(&waker); + + if future.as_mut().poll(context).is_pending() { + *slot = Some(future); + } + } + } + } +} + +impl ArcWake for Task { + fn wake_by_ref(arc_self: &Arc) { + arc_self + .runtime + .sender + .lock() + .unwrap() + .send(arc_self.clone()) + .unwrap(); + } +} + +thread_local! { + static RUNTIME: OnceCell> = OnceCell::new(); +} + +impl RuntimeInner { + fn try_get() -> Option> { + RUNTIME.with(|r| r.get().cloned()) + } + + fn get() -> Arc { + Self::try_get().expect("Runtime::get() called from a thread with no runtime") + } +} + +impl Reactor { + fn new() -> Self { + Self { + inner: Mutex::new(ReactorInner { + poll: PollChannel::new().unwrap(), + event_map: HashMap::new(), + }), + } + } + + fn run(self: Arc) { + loop { + // TODO this is a hack to prevent the lock from becoming fully contended by Reactor + std::thread::sleep(Duration::from_millis(100)); + + let mut inner = self.inner.lock().unwrap(); + let event = inner.poll.wait(Some(Duration::from_millis(100))).unwrap(); + if let Some(fd) = event { + inner.poll.remove(&fd).ok(); + if let Some(task) = inner.event_map.remove(&fd) { + task.wake(); + } + } + } + } + + fn register(&self, interest: &F, waker: Waker) { + let mut inner = self.inner.lock().unwrap(); + let fd = interest.as_raw_fd(); + // TODO have multiple tasks be able to poll the same fd? + assert!(inner.event_map.insert(fd, waker).is_none()); + inner.poll.add(interest).unwrap(); + } +} + +impl Runtime { + pub fn new(threads: usize) -> Self { + let mut executors = vec![]; + let (sender, receiver) = spmc::channel(); + let reactor = Arc::new(Reactor::new()); + let inner = Arc::new(RuntimeInner { + reactor: reactor.clone(), + sender: Mutex::new(sender), + }); + let receiver = Arc::new(receiver); + + // TODO add an option to run reactor on main thread? + let reactor = std::thread::spawn(move || reactor.run()); + + for id in 0..threads { + let receiver = receiver.clone(); + let inner = inner.clone(); + executors.push(std::thread::spawn(move || { + // Initialize local runtime ref + RUNTIME.with(|r| { + if r.set(inner).is_err() { + panic!("Couldn't initialize thread runtime"); + } + }); + + Executor::new(id, receiver).run(); + })); + } + + Self { + inner, + executors, + reactor, + } + } + + pub fn spawn + Send + 'static>(&self, future: F) { + spawn_into(self.inner.clone(), future) + } + + pub fn join(self) { + for executor in self.executors { + executor.join().ok(); + } + self.reactor.join().ok(); + } +} + +fn spawn_into + Send + 'static>(runtime: Arc, future: F) { + let task = Arc::new(Task { + future: Mutex::new(Some(Box::pin(future))), + runtime: runtime.clone(), + }); + runtime.sender.lock().unwrap().send(task).unwrap(); +} + +pub fn spawn + Send + 'static>(future: F) { + let runtime = RuntimeInner::get(); + spawn_into(runtime, future) +} + +pub fn sleep(timeout: Duration) -> Sleep { + let mut timer = TimerFd::new().unwrap(); + timer.start(timeout).unwrap(); + Sleep { timer } +} diff --git a/userspace/lib/yasync/src/main.rs b/userspace/lib/yasync/src/main.rs new file mode 100644 index 00000000..7953863a --- /dev/null +++ b/userspace/lib/yasync/src/main.rs @@ -0,0 +1,23 @@ +use std::time::Duration; + +use yasync::Runtime; + +fn main() { + let runtime = Runtime::new(4); + + runtime.spawn(async move { + loop { + println!("a"); + yasync::sleep(Duration::from_secs(1)).await; + } + }); + + runtime.spawn(async move { + loop { + println!("b"); + yasync::sleep(Duration::from_millis(500)).await; + } + }); + + runtime.join(); +} diff --git a/userspace/rsh/Cargo.toml b/userspace/rsh/Cargo.toml index b51e8c2b..07b06a48 100644 --- a/userspace/rsh/Cargo.toml +++ b/userspace/rsh/Cargo.toml @@ -17,4 +17,3 @@ cross.workspace = true bytemuck.workspace = true smallvec = { version = "1.13.2", features = ["serde"] } -ed25519-dalek = "2.1.1" diff --git a/xtask/src/build/userspace.rs b/xtask/src/build/userspace.rs index 06c23ac4..3c0dfbdb 100644 --- a/xtask/src/build/userspace.rs +++ b/xtask/src/build/userspace.rs @@ -52,6 +52,8 @@ const PROGRAMS: &[(&str, &str)] = &[ // rsh ("rsh", "bin/rsh"), ("rshd", "sbin/rshd"), + // crypt + ("crypt", "bin/rc"), // ("dyn-loader", "libexec/dyn-loader"), ];