diff --git a/Cargo.lock b/Cargo.lock index 5214c824..6d2618fb 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -59,6 +59,12 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" +[[package]] +name = "base64" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "904dfeac50f3cdaba28fc6f57fdcddb75f49ed61346676a78c4ffe55877802fd" + [[package]] name = "bb8" version = "0.7.1" @@ -87,6 +93,22 @@ version = "1.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" +[[package]] +name = "blowfish" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e412e2cd0f2b2d93e02543ceae7917b3c70331573df19ee046bcbc35e45e87d7" +dependencies = [ + "byteorder", + "cipher", +] + +[[package]] +name = "byteorder" +version = "1.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610" + [[package]] name = "bytes" version = "1.1.0" @@ -118,6 +140,16 @@ dependencies = [ "winapi", ] +[[package]] +name = "cipher" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d1873270f8f7942c191139cb8a40fd228da6c3fd2fc376d7e92d47aa14aeb59e" +dependencies = [ + "crypto-common", + "inout", +] + [[package]] name = "clap" version = "2.34.0" @@ -220,6 +252,16 @@ dependencies = [ "winapi", ] +[[package]] +name = "crypto-common" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57952ca27b5e3606ff4dd79b0020231aaf9d6aa76dc05fd30137538c50bd3ce8" +dependencies = [ + "generic-array", + "typenum", +] + [[package]] name = "devtimer" version = "4.0.1" @@ -370,6 +412,16 @@ dependencies = [ "slab", ] +[[package]] +name = "generic-array" +version = "0.14.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fd48d33ec7f05fbfa152300fdad764757cbded343c1aa1cff2fbaf4134851803" +dependencies = [ + "typenum", + "version_check", +] + [[package]] name = "getrandom" version = "0.2.4" @@ -405,6 +457,15 @@ version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4" +[[package]] +name = "inout" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e1f03d4ab4d5dc9ec2d219f86c15d2a15fc08239d1cd3b2d6a19717c0a2f443" +dependencies = [ + "generic-array", +] + [[package]] name = "instant" version = "0.1.12" @@ -810,6 +871,17 @@ dependencies = [ "num_cpus", ] +[[package]] +name = "rcrypt" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c70c3bf2f0b523cc6f2420446e6fca7e14db888cbec907991df043a4d6d2e1e" +dependencies = [ + "base64", + "blowfish", + "getrandom", +] + [[package]] name = "redox_syscall" version = "0.2.10" @@ -1005,6 +1077,7 @@ name = "skyd" version = "0.7.3" dependencies = [ "ahash", + "base64", "bincode", "bytes", "cc", @@ -1021,6 +1094,7 @@ dependencies = [ "openssl", "parking_lot 0.12.0", "rand", + "rcrypt", "regex", "serde", "sky_macros", @@ -1210,6 +1284,12 @@ dependencies = [ "serde", ] +[[package]] +name = "typenum" +version = "1.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dcf81ac59edc17cc8697ff311e8f5ef2d99fcbd9817b34cec66f90b6c3dfd987" + [[package]] name = "unicode-segmentation" version = "1.9.0" diff --git a/server/Cargo.toml b/server/Cargo.toml index 04e782de..cf8497f5 100644 --- a/server/Cargo.toml +++ b/server/Cargo.toml @@ -25,6 +25,8 @@ serde = { version = "1.0.136", features = ["derive"] } tokio = { version = "1.16.1", features = ["full"] } tokio-openssl = "0.6.3" toml = "0.5.8" +rcrypt = "0.4.0" +base64 = "0.13.0" [target.'cfg(all(not(target_env = "msvc"), not(miri)))'.dependencies] # external deps diff --git a/server/src/auth/keys.rs b/server/src/auth/keys.rs new file mode 100644 index 00000000..2c85708a --- /dev/null +++ b/server/src/auth/keys.rs @@ -0,0 +1,54 @@ +/* + * Created on Mon Feb 21 2022 + * + * This file is a part of Skytable + * Skytable (formerly known as TerrabaseDB or Skybase) is a free and open-source + * NoSQL database written by Sayan Nandan ("the Author") with the + * vision to provide flexibility in data modelling without compromising + * on performance, queryability or scalability. + * + * Copyright (c) 2022, Sayan Nandan + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * +*/ + +use crate::corestore::array::Array; + +type AuthkeyArray = Array; + +/// Return a "human readable key" and the "authbytes" that can be stored +/// safely. To do this: +/// - Generate 64 random bytes +/// - Encode that into base64. This is the client key +/// - Hash the key using rcrypt. This is the server key that +/// will be stored +pub fn generate_full() -> (String, super::Authkey) { + let mut bytes: [u8; 64] = [0u8; 64]; + openssl::rand::rand_bytes(&mut bytes).unwrap(); + let ret = base64::encode(&bytes); + let hash = rcrypt::hash(&ret, rcrypt::DEFAULT_COST).unwrap(); + let store_in_db = unsafe { + let mut array = AuthkeyArray::new(); + // we guarantee that the size is equal to 40 + array.extend_from_slice_unchecked(&hash); + array.into_array_unchecked() + }; + (ret, store_in_db) +} + +/// Verify a "human readable key" against the provided "authbytes" +pub fn verify_key(input: &[u8], hash: &[u8]) -> bool { + rcrypt::verify(input, hash).unwrap() +} diff --git a/server/src/auth/mod.rs b/server/src/auth/mod.rs index 646a41f0..901188fd 100644 --- a/server/src/auth/mod.rs +++ b/server/src/auth/mod.rs @@ -26,34 +26,57 @@ #![allow(dead_code)] // TODO(@ohsayan): Remove this once we're done -use crate::corestore::heap_array::HeapArray; -use crate::corestore::lazy::Once; -use crate::corestore::map::Skymap; -use openssl::rand::rand_bytes; +/* + * For our authn/authz, we have two important keys: + * - The origin key: This is the key saved in the configuration file that can also be + * used as the "recovery key" in the event the "root key" is lost. To claim the root + * account, one needs this key. This is a variable width key with a maximum size of + * 64 + * - The root key: This is the superuser key that can be used to create/deny other + * accounts. On claiming the root account, this key is issued + * + * When the root account is claimed, it can be used to create "standard users". Standard + * users have access to everything but the ability to create/revoke other users +*/ + +use crate::corestore::array::Array; +use crate::corestore::htable::Coremap; +use core::mem::MaybeUninit; +use std::sync::Arc; + +mod keys; +#[cfg(test)] +mod tests; +// constants /// Size of an authn key in bytes -const AUTHKEY_SIZE: usize = 64; +const AUTHKEY_SIZE: usize = 40; +/// Size of an authn ID in bytes +const AUTHID_SIZE: usize = 40; +#[sky_macros::array] +const USER_ROOT_ARRAY: [MaybeUninit; 40] = [b'r', b'o', b'o', b't']; +/// The root user +const USER_ROOT: AuthID = unsafe { AuthID::from_const(USER_ROOT_ARRAY, 4) }; /// An authn ID -type AuthID = HeapArray; +type AuthID = Array; /// An authn key -type Authkey = [u8; AUTHKEY_SIZE]; -/// An authn key that can only be assigned to once -type OnceAuthkey = Once; +pub type Authkey = [u8; AUTHKEY_SIZE]; /// Result of an auth operation type AuthResult = Result; /// The authn/authz provider +/// pub struct AuthProvider { - /// the origin key - origin: OnceAuthkey, - /// the root key - root: OnceAuthkey, + origin: Option, + /// the current user + whoami: Option, /// a map of standard users - standard: Skymap, + standard: Arc>, } /// Auth erros +#[derive(PartialEq, Debug)] pub enum AuthError { /// The auth slot was already claimed AlreadyClaimed, @@ -61,40 +84,72 @@ pub enum AuthError { BadCredentials, /// Auth is disabled Disabled, + /// The action is not available to the current account + PermissionDenied, + /// The user is anonymous and doesn't have the right to execute this + Anonymous, } impl AuthProvider { - pub fn new( - origin: Option, - root: Option, - standard: Skymap, - ) -> Self { + pub fn new(standard: Arc>, origin: Option) -> Self { Self { - origin: OnceAuthkey::from(origin), - root: OnceAuthkey::from(root), standard, + whoami: None, + origin, } } - /// Claim the root account - pub fn claim_root(&self, origin: &[u8]) -> AuthResult { - match self.origin.get() { - Some(orig) if orig.eq(origin) => { - let id = Self::generate_full(); - let idc = id.clone(); - if self.root.set(idc) { - Ok(id) - } else { - Err(AuthError::AlreadyClaimed) - } + pub fn claim_root(&self, origin_key: &[u8]) -> AuthResult { + let origin = self.get_origin()?; + if origin == origin_key { + // the origin key was good, let's try claiming root + let (key, store) = keys::generate_full(); + if self.standard.true_if_insert(USER_ROOT, store) { + Ok(key) + } else { + Err(AuthError::AlreadyClaimed) } - Some(_) => Err(AuthError::BadCredentials), + } else { + Err(AuthError::BadCredentials) + } + } + fn are_you_root(&self) -> AuthResult { + match self.whoami.as_ref().map(|v| v.eq(&USER_ROOT)) { + Some(v) => Ok(v), + None => Err(AuthError::Anonymous), + } + } + pub fn claim_user(&self, claimant: &[u8]) -> AuthResult { + if self.are_you_root()? { + self._claim_user(claimant) + } else { + Err(AuthError::PermissionDenied) + } + } + fn _claim_user(&self, claimant: &[u8]) -> AuthResult { + let (key, store) = keys::generate_full(); + if self + .standard + .true_if_insert(Array::try_from_slice(claimant).unwrap(), store) + { + Ok(key) + } else { + Err(AuthError::AlreadyClaimed) + } + } + fn get_origin(&self) -> AuthResult<&Authkey> { + match self.origin.as_ref() { + Some(key) => Ok(key), None => Err(AuthError::Disabled), } } - /// Generate an authentication key - fn generate_full() -> Authkey { - let mut bytes: Authkey = [0u8; AUTHKEY_SIZE]; - rand_bytes(&mut bytes).unwrap(); - bytes +} + +impl Clone for AuthProvider { + fn clone(&self) -> Self { + Self { + standard: self.standard.clone(), + whoami: None, + origin: self.origin, + } } } diff --git a/server/src/auth/tests.rs b/server/src/auth/tests.rs new file mode 100644 index 00000000..9ed8fad7 --- /dev/null +++ b/server/src/auth/tests.rs @@ -0,0 +1,75 @@ +/* + * Created on Tue Feb 22 2022 + * + * This file is a part of Skytable + * Skytable (formerly known as TerrabaseDB or Skybase) is a free and open-source + * NoSQL database written by Sayan Nandan ("the Author") with the + * vision to provide flexibility in data modelling without compromising + * on performance, queryability or scalability. + * + * Copyright (c) 2022, Sayan Nandan + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * +*/ + +mod keys { + use super::super::keys::{generate_full, verify_key}; + + #[test] + fn test_verify_key() { + let (key, store) = generate_full(); + assert!(verify_key(key.as_bytes(), &store)); + } +} + +mod authn { + use super::super::{AuthError, AuthProvider}; + use crate::corestore::htable::Coremap; + use std::sync::Arc; + #[test] + fn claim_root_okay() { + let orig = b"c4299d190fb9a00626797fcc138c56eae9971664"; + let authmap = Arc::new(Coremap::new()); + let provider = AuthProvider::new(authmap, Some(*orig)); + let _ = provider.claim_root(orig).unwrap(); + } + #[test] + fn claim_root_wrongkey() { + let orig = b"c4299d190fb9a00626797fcc138c56eae9971664"; + let authmap = Arc::new(Coremap::new()); + let provider = AuthProvider::new(authmap, Some(*orig)); + let claim_err = provider.claim_root(&orig[1..]).unwrap_err(); + assert_eq!(claim_err, AuthError::BadCredentials); + } + #[test] + fn claim_root_disabled() { + let provider = AuthProvider::new(Arc::new(Coremap::new()), None); + assert_eq!( + provider.claim_root(b"abcd").unwrap_err(), + AuthError::Disabled + ); + } + #[test] + fn claim_root_already_claimed() { + let orig = b"c4299d190fb9a00626797fcc138c56eae9971664"; + let authmap = Arc::new(Coremap::new()); + let provider = AuthProvider::new(authmap, Some(*orig)); + let _ = provider.claim_root(orig).unwrap(); + assert_eq!( + provider.claim_root(orig).unwrap_err(), + AuthError::AlreadyClaimed + ); + } +} diff --git a/server/src/corestore/array.rs b/server/src/corestore/array.rs index 2ff678e9..e21e3e68 100644 --- a/server/src/corestore/array.rs +++ b/server/src/corestore/array.rs @@ -33,7 +33,6 @@ use core::fmt; use core::hash::Hash; use core::hash::Hasher; use core::iter::FromIterator; -use core::marker::PhantomData; use core::mem::ManuallyDrop; use core::mem::MaybeUninit; use core::ops; @@ -90,14 +89,6 @@ impl<'a, T: Copy> Drop for LenScopeGuard<'a, T> { } } -// defy the compiler; just some silly hackery here -- move on -struct UninitArray(PhantomData); - -impl UninitArray { - const VALUE: MaybeUninit = MaybeUninit::uninit(); - const ARRAY: [MaybeUninit; N] = [Self::VALUE; N]; -} - macro_rules! impl_zeroed_nm { ($($ty:ty),* $(,)?) => { $( @@ -118,10 +109,13 @@ impl_zeroed_nm! { } impl Array { + // just some silly hackery here because uninit_array isn't stabilized -- move on + const VALUE: MaybeUninit = MaybeUninit::uninit(); + const ARRAY: [MaybeUninit; N] = [Self::VALUE; N]; /// Create a new array pub const fn new() -> Self { Array { - stack: UninitArray::ARRAY, + stack: Self::ARRAY, init_len: 0, } } @@ -140,7 +134,7 @@ impl Array { /// This function is extremely unsafe. I mean, I don't even know how to call it safe. /// There's one way though: make M == N. This will panic in debug mode if M > N. In /// release mode, good luck - pub unsafe fn from_const_array(arr: [T; M]) -> Self { + unsafe fn from_const_array(arr: [T; M]) -> Self { debug_assert!( N >= M, "Provided const array exceeds size limit of initialized array" @@ -278,13 +272,20 @@ impl Array { // not fully initialized Err(self) } else { - unsafe { - Ok({ - // make sure we don't do a double free or end up deleting the elements - let _self = ManuallyDrop::new(self); - ptr::read(_self.as_ptr() as *const [T; N]) - }) - } + unsafe { Ok(self.into_array_unchecked()) } + } + } + pub unsafe fn into_array_unchecked(self) -> [T; N] { + // make sure we don't do a double free or end up deleting the elements + let _self = ManuallyDrop::new(self); + ptr::read(_self.as_ptr() as *const [T; N]) + } + pub fn try_from_slice(slice: impl AsRef<[T]>) -> Option { + let slice = slice.as_ref(); + if slice.len() != N { + None + } else { + Some(unsafe { Self::from_slice(slice) }) } } /// Extend self from a slice @@ -408,7 +409,7 @@ impl Array { unsafe { // the ptr to start writing from let mut ptr = Self::as_mut_ptr(self).add(self.len()); - let end_ptr = Self::as_ptr(self).add(self.remaining_cap()); + let end_ptr = Self::as_ptr(self).add(self.capacity()); let mut guard = LenScopeGuard::new(&mut self.init_len); let mut iter = iterable.into_iter(); loop { @@ -623,6 +624,16 @@ fn test_array_clone() { assert_eq!(arr, myclone); } +#[test] +fn test_array_extend_okay() { + let mut arr: Array = Array::new(); + arr.extend( + "qHwRsmyBYHbqyHfdShOfVSayVUmeKlEagvJoGuTyvaCqpsfFkZabeuqmVeiKbJxV" + .as_bytes() + .to_owned(), + ); +} + #[test] #[should_panic] fn test_array_extend_fail() { diff --git a/server/src/corestore/lazy.rs b/server/src/corestore/lazy.rs index 9bea29d6..30cb7bdb 100644 --- a/server/src/corestore/lazy.rs +++ b/server/src/corestore/lazy.rs @@ -212,6 +212,7 @@ pub struct Once { value: AtomicPtr, } +#[allow(dead_code)] // TODO: Remove this impl Once { pub const fn new() -> Self { Self { diff --git a/server/src/corestore/memstore.rs b/server/src/corestore/memstore.rs index 774dd57d..260c1701 100644 --- a/server/src/corestore/memstore.rs +++ b/server/src/corestore/memstore.rs @@ -199,6 +199,10 @@ impl Memstore { }, } } + /// Get a reference to the system keyspace + pub fn get_system_keyspace(&self) -> Arc { + self.get_keyspace_atomic_ref(&SYSTEM).unwrap() + } /// Get an atomic reference to a keyspace pub fn get_keyspace_atomic_ref(&self, keyspace_identifier: &Q) -> Option> where