use crate::{
api::ipc::{
req::{AuthReq, IpcReq},
resp::{AuthGranted, IpcResp},
IpcMsg,
},
Error, Result, SafeAuthReq,
};
use hmac::Hmac;
use log::{debug, info, trace};
use rand::rngs::{OsRng, StdRng};
use rand_core::SeedableRng;
use sha3::Sha3_256;
use sn_client::{client::Client, Error as ClientError, ErrorMessage::NoSuchEntry};
use sn_data_types::{
Keypair, MapAction, MapAddress, MapEntryActions, MapPermissionSet, MapSeqEntryActions,
MapValue, Token,
};
use std::{
collections::{BTreeMap, HashSet},
net::SocketAddr,
path::{Path, PathBuf},
};
use tiny_keccak::{Hasher, Sha3};
use xor_name::{XorName, XOR_NAME_LEN};
const SHA3_512_HASH_LEN: usize = 64;
const SAFE_TYPE_TAG: u64 = 1_300;
const DEFAULT_TEST_COINS_AMOUNT: u64 = 777_000_000_000;
pub fn derive_secrets(acc_passphrase: &[u8], acc_password: &[u8]) -> (Vec<u8>, Vec<u8>, Vec<u8>) {
let mut passphrase_hasher = Sha3::v512();
let mut passphrase_hash = [0; SHA3_512_HASH_LEN];
passphrase_hasher.update(&acc_passphrase);
passphrase_hasher.finalize(&mut passphrase_hash);
let passphrase = passphrase_hash.to_vec();
let mut salt_hasher = Sha3::v512();
let mut salt_hash = [0; SHA3_512_HASH_LEN];
let salt_bytes = &passphrase_hash[SHA3_512_HASH_LEN / 2..];
salt_hasher.update(&salt_bytes);
salt_hasher.finalize(&mut salt_hash);
let salt = salt_hash.to_vec();
let mut password_hasher = Sha3::v512();
let mut password_hash = [0; SHA3_512_HASH_LEN];
password_hasher.update(&acc_password);
password_hasher.finalize(&mut password_hash);
let password = password_hash.to_vec();
(passphrase, password, salt)
}
fn create_ed25519_keypair_from_seed(seeder: &[u8]) -> Keypair {
let mut hasher = Sha3::v256();
let mut seed = [0; 32];
hasher.update(&seeder);
hasher.finalize(&mut seed);
let mut rng = StdRng::from_seed(seed);
Keypair::new_ed25519(&mut rng)
}
pub fn derive_location_and_keypair(passphrase: &str, password: &str) -> Result<(XorName, Keypair)> {
let (passphrase, password, salt) = derive_secrets(passphrase.as_bytes(), password.as_bytes());
let map_data_location = generate_network_address(&passphrase, &salt)?;
let mut seed = password;
seed.extend(salt.iter());
let keypair = create_ed25519_keypair_from_seed(&seed);
Ok((map_data_location, keypair))
}
pub fn generate_network_address(passphrase: &[u8], salt: &[u8]) -> Result<XorName> {
let mut id = XorName([0; XOR_NAME_LEN]);
const ITERATIONS: u32 = 10_000u32;
pbkdf2::pbkdf2::<Hmac<Sha3_256>>(passphrase, &salt, ITERATIONS, &mut id.0[..]);
Ok(id)
}
#[derive(Default)]
pub struct SafeAuthenticator {
safe: Option<(Client, MapAddress)>,
config_path: Option<PathBuf>,
bootstrap_contacts: Option<HashSet<SocketAddr>>,
}
impl SafeAuthenticator {
pub fn new(
config_dir_path: Option<&Path>,
bootstrap_contacts: Option<HashSet<SocketAddr>>,
) -> Self {
let config_path = config_dir_path.map(|p| p.to_path_buf());
Self {
safe: None,
config_path,
bootstrap_contacts,
}
}
pub async fn create(&mut self, passphrase: &str, password: &str) -> Result<()> {
debug!("Attempting to create a Safe from provided passphrase and password.");
let (location, keypair) = derive_location_and_keypair(passphrase, password)?;
let data_owner = keypair.public_key();
debug!("Creating Safe to be owned by PublicKey: {:?}", data_owner);
let mut client = Client::new(
Some(keypair),
self.config_path.as_deref(),
self.bootstrap_contacts.clone(),
)
.await?;
trace!("Client instantiated properly!");
let existing_balance = client.get_balance().await?;
if existing_balance != Token::from_nano(0) {
return Err(Error::AuthenticatorError(
"Client data already exists".to_string(),
));
}
client
.trigger_simulated_farming_payout(Token::from_nano(DEFAULT_TEST_COINS_AMOUNT))
.await?;
let permission_set = MapPermissionSet::new()
.allow(MapAction::Read)
.allow(MapAction::Insert)
.allow(MapAction::Update)
.allow(MapAction::Delete)
.allow(MapAction::ManagePermissions);
let mut permission_map = BTreeMap::new();
permission_map.insert(data_owner, permission_set);
let map_address = client
.store_seq_map(
location,
SAFE_TYPE_TAG,
data_owner,
None,
Some(permission_map),
)
.await
.map_err(|err| {
Error::AuthenticatorError(format!("Failed to store Safe on a Map: {}", err))
})?;
debug!("Map stored successfully for new Safe!");
self.safe = Some((client, map_address));
Ok(())
}
pub async fn unlock(&mut self, passphrase: &str, password: &str) -> Result<()> {
debug!("Attempting to unlock a Safe...");
let (location, keypair) = derive_location_and_keypair(passphrase, password)?;
debug!(
"Unlocking Safe owned by PublicKey: {:?}",
keypair.public_key()
);
let client = Client::new(
Some(keypair),
self.config_path.as_deref(),
self.bootstrap_contacts.clone(),
)
.await?;
trace!("Client instantiated properly!");
let map_address = MapAddress::Seq {
name: location,
tag: SAFE_TYPE_TAG,
};
let _ = client.get_map(map_address).await?;
debug!("Safe unlocked successfully!");
self.safe = Some((client, map_address));
Ok(())
}
pub fn lock(&mut self) -> Result<()> {
debug!("Locking Safe...");
self.safe = None;
Ok(())
}
pub fn is_a_safe_unlocked(&self) -> bool {
let is_a_safe_unlocked = self.safe.is_some();
debug!(
"Is there a Safe currently unlocked?: {}",
is_a_safe_unlocked
);
is_a_safe_unlocked
}
pub async fn decode_req(&self, req: &str) -> Result<SafeAuthReq> {
match IpcMsg::from_string(req) {
Ok(IpcMsg::Req(IpcReq::Auth(app_auth_req))) => {
debug!("Auth request string decoded: {:?}", app_auth_req);
Ok(SafeAuthReq::Auth(app_auth_req))
}
Ok(other) => Err(Error::AuthError(format!(
"Failed to decode string as an authorisation request, it's a: '{:?}'",
other
))),
Err(error) => Err(Error::AuthenticatorError(format!(
"Failed to decode request: {:?}",
error
))),
}
}
pub async fn revoke_app(&self, _y: &str) -> Result<()> {
unimplemented!()
}
pub async fn authorise_app(&self, req: &str) -> Result<String> {
let ipc_req = IpcMsg::from_string(req).map_err(|err| {
Error::AuthenticatorError(format!("Failed to decode authorisation request: {:?}", err))
})?;
debug!("Auth request string decoded: {:?}", ipc_req);
match ipc_req {
IpcMsg::Req(IpcReq::Auth(app_auth_req)) => {
info!("Request was recognised as an application auth request");
debug!("Decoded request: {:?}", app_auth_req);
self.gen_auth_response(app_auth_req).await
}
IpcMsg::Req(IpcReq::Unregistered(user_data)) => {
info!("Request was recognised as an unregistered auth request");
debug!("Decoded request: {:?}", user_data);
self.gen_unreg_auth_response()
}
IpcMsg::Resp { .. } | IpcMsg::Err(..) => Err(Error::AuthError(
"The request was not recognised as a valid auth request".to_string(),
)),
}
}
pub async fn authenticate(&self, auth_req: AuthReq) -> Result<AuthGranted> {
debug!(
"Retrieving/generating keypair for an application: {:?}",
auth_req
);
if let Some((client, map_address)) = &self.safe {
let app_id = auth_req.app_id.as_bytes().to_vec();
let keypair = match client.get_map_value(*map_address, app_id.clone()).await {
Ok(value) => {
trace!(
"The app ('{}') already has a Keypair in the Safe",
auth_req.app_id
);
let keypair_bytes = match value {
MapValue::Seq(seq_value) => seq_value.data,
MapValue::Unseq(data) => data,
};
let keypair_str = String::from_utf8(keypair_bytes).map_err(|_err| {
Error::AuthError(
"The Safe contains an invalid keypair associated to this app"
.to_string(),
)
})?;
let keypair: Keypair = serde_json::from_str(&keypair_str).map_err(|_err| {
Error::AuthError(
"The Safe contains an invalid keypair associated to this app"
.to_string(),
)
})?;
debug!(
"Keypair for the app being authorised ('{}') retrieved from the Safe: {}",
auth_req.app_id,
keypair.public_key()
);
keypair
}
Err(ClientError::ErrorMessage(NoSuchEntry)) => {
trace!(
"The app ('{}') was not assigned a Keypair yet in the Safe. Generating one for it...",
auth_req.app_id
);
let mut rng = OsRng;
let keypair = Keypair::new_ed25519(&mut rng);
let keypair_str = serde_json::to_string(&keypair).map_err(|err| {
Error::AuthError(format!(
"Failed to serialised keypair to store it in the Safe: {}",
err
))
})?;
debug!(
"New keypair generated for app ('{}') being authorised: {}",
auth_req.app_id,
keypair.public_key()
);
let mut tmp_client = Client::new(
Some(keypair.clone()),
self.config_path.as_deref(),
self.bootstrap_contacts.clone(),
)
.await?;
tmp_client
.trigger_simulated_farming_payout(Token::from_nano(
DEFAULT_TEST_COINS_AMOUNT,
))
.await?;
let map_actions =
MapSeqEntryActions::new().ins(app_id, keypair_str.as_bytes().to_vec(), 0);
client
.edit_map_entries(*map_address, MapEntryActions::Seq(map_actions))
.await?;
keypair
}
Err(err) => {
return Err(Error::AuthError(format!(
"Failed to retrieve keypair from the Safe: {}",
err
)))
}
};
Ok(AuthGranted {
app_keypair: keypair,
bootstrap_config: self.bootstrap_contacts.clone(),
})
} else {
Err(Error::AuthenticatorError(
"No Safe is currently unlocked".to_string(),
))
}
}
async fn gen_auth_response(&self, auth_req: AuthReq) -> Result<String> {
let auth_granted = self.authenticate(auth_req).await.map_err(|err| {
Error::AuthenticatorError(format!(
"Failed to authorise application on the network: {}",
err
))
})?;
debug!("Encoding response with auth credentials auth granted...");
let resp = serde_json::to_string(&IpcMsg::Resp(IpcResp::Auth(Ok(auth_granted)))).map_err(
|err| Error::AuthenticatorError(format!("Failed to encode response: {:?}", err)),
)?;
debug!("Returning auth response generated");
Ok(resp)
}
fn gen_unreg_auth_response(&self) -> Result<String> {
let bootstrap_contacts = self.bootstrap_contacts.clone().ok_or_else(|| {
Error::AuthenticatorError("Bootstrap contacts information not available".to_string())
})?;
debug!("Encoding response... {:?}", bootstrap_contacts);
let resp =
serde_json::to_string(&IpcMsg::Resp(IpcResp::Unregistered(Ok(bootstrap_contacts))))
.map_err(|err| {
Error::AuthenticatorError(format!("Failed to encode response: {:?}", err))
})?;
debug!("Returning unregistered auth response generated: {:?}", resp);
Ok(resp)
}
}
#[cfg(test)]
mod tests {
use super::*;
use anyhow::{Context, Result};
use proptest::prelude::*;
use sn_data_types::PublicKey;
#[test]
fn get_deterministic_pk_from_known_seed() -> Result<()> {
let seed = b"bacon";
let pk = create_ed25519_keypair_from_seed(seed).public_key();
let public_key_bytes: [u8; ed25519_dalek::PUBLIC_KEY_LENGTH] = [
239, 124, 31, 157, 76, 101, 124, 119, 164, 143, 80, 234, 249, 84, 0, 22, 91, 128, 67,
92, 39, 182, 197, 184, 83, 44, 41, 127, 78, 175, 205, 198,
];
let ed_pk = ed25519_dalek::PublicKey::from_bytes(&public_key_bytes)
.with_context(|| "Cannot deserialise expected key".to_string())?;
let expected_pk = PublicKey::from(ed_pk);
assert_eq!(pk, expected_pk);
Ok(())
}
proptest! {
#[test]
fn proptest_always_get_same_info_from_from_phrase_and_pw(s in "\\PC*", p in "\\PC*") {
let (location, keypair) = derive_location_and_keypair(&s, &p).expect("could not derive location/keypair");
let (location_again, keypair_again) = derive_location_and_keypair(&s, &p).expect("could not derive location/keypair");
prop_assert_eq!(location, location_again);
prop_assert_eq!(keypair, keypair_again);
}
}
}