Browse Source

Redo some docs, remove some superflous ones.

tags/v0.1.0
Thomas Kerber 2 years ago
parent
commit
560d2f61d5

+ 5
- 0
README.md View File

@@ -12,3 +12,8 @@ goblin's is designed to manage passwords in a version controlled container,
allowing reverting to previous values and merging keyrings on different
devices. It is primarily controlled through a [jsonrpc](http://www.jsonrpc.org)
API, allowing users to easily extend the core functionality.

## Documentation

* [`goblin-core`](https://doc.drwx.org/goblin/goblin_core/)


+ 21
- 7
goblin-core/src/crypto.rs View File

@@ -10,22 +10,22 @@ use zero::ZeroBox;
/// A credential which can be used to unlock a vault.
#[derive(Clone, Copy)]
pub enum Credential<'a> {
/// A symmetric encryption key, used directly.
#[allow(missing_docs)]
#[doc(hidden)]
Key(&'a Key),
/// A passphrase used to generate and retrieve the symmetric encryption key.
#[allow(missing_docs)]
Passphrase(&'a [u8]),
}

#[derive(Serialize, Deserialize, PartialEq, Eq, Clone, Copy, PartialOrd, Ord, Hash)]
/// A sha3-256 hash.
#[derive(Serialize, Deserialize, PartialEq, Eq, Clone, Copy, PartialOrd, Ord, Hash)]
pub struct Hash(
#[serde(serialize_with = "as_hex", deserialize_with = "from_hex32")]
[u8; 32]
);

impl Hash {
/// Create a hash of the given data.
#[allow(missing_docs)]
pub fn new(data: &[u8]) -> Self {
let mut ret = [0u8; 32];
digest::hash(digest::Algorithm::Sha3_256, data, &mut ret[..]);
@@ -45,7 +45,7 @@ impl AsRef<[u8]> for Hash {
}
}

/// Describes a 128-bit salt, used for secure key generation.
/// A 128-bit salt, used for secure key derivation.
#[derive(Serialize, Deserialize)]
pub struct Salt(
#[serde(serialize_with = "as_hex", deserialize_with = "from_hex16")]
@@ -53,7 +53,7 @@ pub struct Salt(
);

impl Salt {
/// Creates a new salt from a secure random pool.
#[allow(missing_docs)]
pub fn new() -> Self {
let mut salt = [0u8; 16];
randomize(&mut salt[..]);
@@ -72,7 +72,7 @@ impl AsRef<[u8]> for Salt {
pub struct Key(ZeroBox<[u8; 32]>);

impl Key {
/// Generates a new, random key.
#[allow(missing_docs)]
pub fn new() -> Self {
let mut key = [0u8; 32];
randomize(&mut key[..]);
@@ -127,6 +127,9 @@ impl AsRef<[u8]> for Key {
}
}

/// Encrypts and writes out some data using AES-CTR-256.
///
/// A new 256-bit salt is prepended to the data before writing it out.
pub fn encrypt<W: Write>(mut data: Vec<u8>, key: &Key, mut out: W) -> Result<()> {
let mut ctr = [0u8; 16];
randomize(&mut ctr[..]);
@@ -139,6 +142,7 @@ pub fn encrypt<W: Write>(mut data: Vec<u8>, key: &Key, mut out: W) -> Result<()>
Ok(())
}

/// Reads and decrypts data, as inverse of `encrypt`.
pub fn decrypt<R: Read>(key: &Key, mut input: R) -> Result<ZeroBox<Vec<u8>>> {
let mut ctr = [0u8; 16];
let mut data = Vec::new();
@@ -151,6 +155,7 @@ pub fn decrypt<R: Read>(key: &Key, mut input: R) -> Result<ZeroBox<Vec<u8>>> {
Ok(ZeroBox::new(data))
}

/// Generates cryptographically secure randomness in a buffer.
fn randomize(buf: &mut [u8]) {
rand::randomize(rand::Level::Strong, buf);
}
@@ -165,4 +170,13 @@ mod tests {
let keyfile = [0u8; 64];
assert!(Key::from_keyfile(&keyfile[..], &Key::new()).is_err());
}

#[test]
fn test_decrypt() {
let key = Key::new();
let data = "foobar".as_bytes().to_owned();
let mut enc = Vec::new();
encrypt(data, &key, &mut enc).unwrap();
assert_eq!(AsRef::<[u8]>::as_ref(&decrypt(&key, &enc[..]).unwrap()), "foobar".as_bytes());
}
}

+ 3
- 2
goblin-core/src/err.rs View File

@@ -14,12 +14,12 @@ use std::sync::PoisonError;
#[cfg(all(feature = "basedirs", unix))]
use xdg::BaseDirectoriesError;

/// The result of an operation which may fail within goblin core.
#[allow(missing_docs)]
pub type Result<T> = result::Result<T, Error>;

use Error::*;

/// An error from within goblin core.
#[allow(missing_docs)]
#[derive(Debug)]
pub enum Error {
/// An error from the gcrypt library.
@@ -73,6 +73,7 @@ pub enum Error {
InvalidUrl(String),
/// The hash of an improperly initialized commit was accessed.
ImproperCommit,
/// Can't actually occur, to stop API breakage on adding errors.
#[doc(hidden)]
Nonexhaustive,
}

+ 28
- 32
goblin-core/src/format.rs View File

@@ -32,6 +32,9 @@ pub struct Vault {
}

impl Vault {
/// Loads a vault, but does not yet unlock it.
///
/// Must be followed by an unlock.
fn load(location: VaultLocation) -> Result<Self> {
let memtar = MemTar::open(Cursor::new(location.open()?))?;
let md: MetaData = serde_json::from_slice(memtar.entry("metadata.json")?)?;
@@ -49,14 +52,14 @@ impl Vault {
})
}

/// Opens an existing vault at the given location with the given credentials.
#[allow(missing_docs)]
pub fn open(location: VaultLocation, cred: Credential) -> Result<Self> {
let mut vault = Vault::load(location)?;
vault.unlock(cred)?;
Ok(vault)
}

/// Retrieves a list of all commit hashes contained within the vault.
#[allow(missing_docs)]
pub fn commits(&self) -> Vec<Hash> {
self.memtar
.keys()
@@ -90,7 +93,7 @@ impl Vault {
.collect()
}

/// Retrieves the head commit, or `None` if there is none.
#[allow(missing_docs)]
pub fn head_commit(&self) -> Result<Option<Commit>> {
Ok(match self.metadata.head {
Some(h) => Some(Commit::from_hash(&self, h)?),
@@ -98,7 +101,7 @@ impl Vault {
})
}

/// Retrieves the hash of the head commit, or `None` if there is none.
#[allow(missing_docs)]
pub fn head(&self) -> Option<Hash> {
self.metadata.head
}
@@ -133,7 +136,7 @@ impl Vault {
Ok(())
}

/// Returns the location the vault is saved at.
#[allow(missing_docs)]
pub fn location(&self) -> &VaultLocation {
&self.location
}
@@ -145,7 +148,7 @@ impl From<VaultMut> for Vault {
}
}

/// A mutable `Vault`.
#[allow(missing_docs)]
pub struct VaultMut {
modified: bool,
// May ONLY be None if the item was removed during deconstruction. This drops drop from doing
@@ -157,7 +160,7 @@ impl VaultMut {
/// Merges a second vault into this one.
///
/// This operation imports new files from the second vault, and returns a vector of head
/// commits (usually, and at most, two).
/// commits (usually, and at most, two). It does not start the creation of a merge commit.
pub fn merge(&mut self, sec: VaultLocation) -> Result<Vec<Hash>> {
info!("merging vault '{}' into '{}'", sec, self.location);
let v = Vault::open(sec, Credential::Key(self.key.as_ref().unwrap()))?;
@@ -171,9 +174,7 @@ impl VaultMut {
)
}

/// Creates a new vault at the given file with the given passphrase.
///
/// Note that the vault file is not actually created until the vault is closed.
#[allow(missing_docs)]
pub fn create(location: VaultLocation, passphrase: &[u8]) -> Result<Self> {
info!("creating new vault '{}'", location);
let mut vault = VaultMut {
@@ -196,7 +197,7 @@ impl VaultMut {
Ok(vault)
}

/// Resets the vault to the given commit hash, if it exists.
#[allow(missing_docs)]
pub fn reset(&mut self, commit: Hash) -> Result<()> {
// Ensure the commit exists
Commit::from_hash(self, commit)?;
@@ -230,9 +231,6 @@ impl VaultMut {
Ok(())
}

/// Closes the vault, writing any changes to the vault file.
///
/// This is also called when the vault is dropped, although errors are ignored.
pub fn close(mut self) -> Result<()> {
self.flush()
}
@@ -252,7 +250,7 @@ impl VaultMut {
Ok(())
}

/// Adds a new passphrase.
#[allow(missing_docs)]
pub fn add_passphrase(&mut self, passphrase: &[u8]) -> Result<()> {
let derived_key = Key::derive(passphrase, &self.metadata.salt)?;
let path = format!("{}.key", Hash::new(derived_key.as_ref()));
@@ -288,7 +286,7 @@ impl VaultMut {
Ok(())
}

/// Returns the immutable vault contained within.
#[allow(missing_docs)]
pub fn into_inner(mut self) -> Vault {
// duplicate drop, as drop will not function after this.
self.flush().ok();
@@ -331,13 +329,13 @@ impl Drop for VaultMut {
}
}

/// Defines a change to the keyring.
/// Defines an atomic change to the keyring.
#[derive(Serialize, Deserialize, Clone)]
#[serde(tag = "op", content = "arg", rename_all = "snake_case")]
pub enum Delta {
/// Inserts a new value at the given path.
#[allow(missing_docs)]
Insert(Path, Value),
/// Removes the value at the given path.
#[allow(missing_docs)]
Rm(Path),
/// Moves a value from the first path to the second.
Mv(Path, Path),
@@ -402,23 +400,20 @@ impl Delta {
#[derive(Serialize, Deserialize, Clone)]
pub struct Commit {
/// The previous commit hashes.
///
/// If empty, the commit is an initial commit.
///
/// If contains multiple previous commits, the commit is a merge commit.
pub previous: Vec<Hash>,
/// The commit's creation time.
#[serde(serialize_with = "ser_time", deserialize_with = "de_time")]
pub ctime: SystemTime,
/// The deltas composing the commit.
/// The deltas composing the commit, in the order they are applied.
pub deltas: Vec<Delta>,
/// The commit hash
/// The commit hash.
#[serde(skip)]
pub hash: Option<Hash>,
}

impl Commit {
/// Creates a new commit in the given vault for a set of deltas.
/// Creates a new commit with the head as its predecessor.
#[allow(missing_docs)]
pub fn new(vault: &mut VaultMut, deltas: Vec<Delta>) -> StdResult<Self, (Vec<Delta>, Error)> {
let pred = if let Some(hash) = vault.metadata.head {
vec![hash]
@@ -428,7 +423,7 @@ impl Commit {
Commit::new_full(vault, deltas, pred).map_err(|(d, _, e)| (d, e))
}

/// Creates a new commit in the given vault for a set of deltas and predecessor hashes.
#[allow(missing_docs)]
pub fn new_full(
vault: &mut VaultMut,
deltas: Vec<Delta>,
@@ -473,7 +468,7 @@ impl Commit {
Ok(())
}

/// Retrieves a commit from the given hash.
#[allow(missing_docs)]
pub fn from_hash(vault: &Vault, hash: Hash) -> Result<Self> {
let path = format!("{}.commit", hash);
let mut f = &vault.memtar.entry(&path)?[..];
@@ -491,7 +486,7 @@ impl Commit {
Ok(ret)
}

/// Retrieves the previous commits.
#[allow(missing_docs)]
pub fn previous(&self, vault: &Vault) -> Result<Vec<Self>> {
let mut ret = Vec::with_capacity(self.previous.len());
for h in &self.previous {
@@ -511,10 +506,11 @@ impl Commit {

/// Retrieves the immediate dominator of a set of commits.
///
/// This is playing somewhat loose with the definition of immediate dominator;
/// here it is taken to mean the node that strictly dominates each commit in the set, but does
/// not strictly dominate any other node that strictly dominates each commit in the set.
/// This is playing somewhat loose with the definition of immediate dominator; here it is taken
/// to mean the node that strictly dominates each commit in the set, but does not strictly
/// dominate any other node that strictly dominates each commit in the set.
pub fn idom_mult(commits: Vec<Commit>, vault: &Vault) -> Result<Option<Self>> {
// TODO: rewrite, this is a mess. Possibly create a graph module just for this?
if commits.len() == 0 {
return Ok(None);
}

+ 1
- 2
goblin-core/src/ipc/rpc_def.rs View File

@@ -4,11 +4,10 @@ use format::{Commit, Delta};
use jsonrpc_core::types::error::{Error, ErrorCode};
use keyring::{self, Path, Value};
use location::VaultLocation;
use perm::{Access, PermissionModel, UnlockSpec};
use perm::{Access, AccessControl, UnlockSpec};
use serde_json;
use state::VaultState;
use std::error::Error as StdError;
use std::str::FromStr;
use std::sync::{Arc, PoisonError, RwLock};
use zero::ZeroBox;


+ 10
- 11
goblin-core/src/keyring.rs View File

@@ -4,7 +4,7 @@ use format::{Commit, Delta, Vault};
use std::collections::HashMap;
use zero::ZeroBox;

/// Used to identify elements of the keyring.
/// A UNIX path-like identifier of values in a keyring.
pub type Path = ZeroBox<String>;

impl Path {
@@ -48,7 +48,7 @@ impl<'a> From<&'a str> for Path {
/// Used to identify elements of the keyring.
pub type Value = ZeroBox<String>;

/// Describes the value of a keyring entry.
/// An entry in a keyring.
#[derive(Clone, PartialEq, Eq, Debug)]
pub enum Entry {
/// A raw data value, e.g. a password.
@@ -58,7 +58,7 @@ pub enum Entry {
}

impl Entry {
/// Constructs a value entry from a string.
/// Convinience constructor for value entries.
pub fn value(val: &str) -> Self {
Entry::Value(ZeroBox::new(val.to_owned()))
}
@@ -69,12 +69,11 @@ impl Entry {
pub struct Keyring(HashMap<Path, Entry>);

impl Keyring {
/// Creates a new, empty keyring.
pub fn empty() -> Self {
Keyring(HashMap::new())
}

/// Constructs a keyring from a given vault.
#[allow(missing_docs)]
pub fn from_vault(vault: &Vault) -> Result<Self> {
info!("constructing keyring");
if let Some(i) = vault.head_commit()? {
@@ -84,12 +83,12 @@ impl Keyring {
}
}

/// Constructs a keyring from a given commit hash.
#[allow(missing_docs)]
pub fn from_hash(vault: &Vault, hash: Hash) -> Result<Self> {
Self::from_commit(vault, Commit::from_hash(vault, hash)?)
}

/// Constructs a keyring from a given commit.
#[allow(missing_docs)]
pub fn from_commit(vault: &Vault, mut commit: Commit) -> Result<Self> {
debug!("constructing keyring from commit {}", commit.hash()?);
let mut hist_chain = vec![];
@@ -176,7 +175,7 @@ impl Keyring {
);
}

/// Replays a commit on the keyring.
#[allow(missing_docs)]
pub fn replay(&mut self, commit: Commit) {
if let Ok(h) = commit.hash() {
debug!("replaying commit {}", h);
@@ -186,7 +185,7 @@ impl Keyring {
}
}

/// Applies a delta to the keyring.
#[allow(missing_docs)]
pub fn apply(&mut self, delta: &Delta) {
match delta {
&Delta::Insert(ref k, ref v) => {
@@ -232,7 +231,7 @@ impl Keyring {
}
}

/// Gets a raw value from the keyring.
#[allow(missing_docs)]
pub fn get_raw<T: AsRef<str>>(&self, path: T) -> Option<&Entry> {
self.0.get(path.as_ref())
}
@@ -250,7 +249,7 @@ impl Keyring {
}
}

/// Returns a list of all paths in the keyring.
#[allow(missing_docs)]
pub fn keys(&self) -> Vec<&Path> {
self.0.keys().collect()
}

+ 2
- 3
goblin-core/src/location.rs View File

@@ -17,12 +17,11 @@ use std::str::FromStr;
#[cfg(all(feature = "basedirs", unix))]
use xdg::BaseDirectories;

/// Describes where a vault is stored
#[allow(missing_docs)]
#[derive(Debug, Clone)]
pub enum VaultLocation {
/// In a file on the local system.
#[allow(missing_docs)]
File(PathBuf),
/// In a file over ssh.
#[cfg(feature = "ssh")]
#[doc(hidden)]
Ssh(ssh::FileRef),

+ 7
- 0
goblin-core/src/memtar.rs View File

@@ -4,6 +4,7 @@ use std::ops::Deref;
use std::path::{Path, PathBuf};
use tar;

/// A tar archive in memory.
#[derive(Clone)]
pub struct MemTar {
memmap: HashMap<PathBuf, MemEntry>,
@@ -14,6 +15,7 @@ impl MemTar {
MemTar { memmap: HashMap::new() }
}

/// Reads a tar archive into memory.
pub fn open<R: Read>(r: R) -> io::Result<Self> {
let mut map = HashMap::new();
for entry in tar::Archive::new(r).entries()? {
@@ -24,6 +26,7 @@ impl MemTar {
Ok(MemTar { memmap: map })
}

/// Writes the tar archive out.
pub fn export<W: Write>(&self, w: W) -> io::Result<()> {
let mut builder = tar::Builder::new(w);
for (_, entry) in self.memmap.iter() {
@@ -33,6 +36,7 @@ impl MemTar {
Ok(())
}

/// Gets a path within the tar file.
pub fn entry<P: AsRef<Path>>(&self, path: P) -> io::Result<&MemEntry> {
if let Some(entry) = self.memmap.get(path.as_ref()) {
Ok(entry)
@@ -44,6 +48,7 @@ impl MemTar {
}
}

#[allow(missing_docs)]
pub fn create<P: AsRef<Path>>(&mut self, path: P, data: Vec<u8>) -> io::Result<()> {
self.memmap.insert(
path.as_ref().to_owned(),
@@ -52,6 +57,7 @@ impl MemTar {
Ok(())
}

#[allow(missing_docs)]
pub fn rm<P: AsRef<Path>>(&mut self, path: P) -> io::Result<()> {
if self.memmap.remove(path.as_ref()).is_some() {
Ok(())
@@ -82,6 +88,7 @@ impl Deref for MemTar {
}
}

/// A file in a memory tar.
#[derive(Clone)]
pub struct MemEntry {
header: tar::Header,

+ 15
- 4
goblin-core/src/perm.rs View File

@@ -7,7 +7,8 @@ use std::time::{Duration, Instant};

const DEFAULT_TIMEOUT_SECS: u64 = 120;

pub trait PermissionModel {
#[allow(missing_docs)]
pub trait AccessControl {
fn allowed(&self, access: &Access) -> bool;

fn request(&self, access: &Access) -> Result<()> {
@@ -19,43 +20,53 @@ pub trait PermissionModel {
}
}

/// A set of unlock operations defining the permissions to a keyring state.
#[allow(missing_docs)]
pub struct Permissions(Vec<Unlock>);

impl Permissions {
#[allow(missing_docs)]
pub fn new() -> Self {
Permissions(Vec::new())
}

/// Removes dead unlocks from the permission set.
pub fn purge(&mut self) -> bool {
let t = Instant::now();
self.0.retain(|u| !u.expired(t));
self.0.len() == 0
}

/// Whether any of the permissions may require write access.
pub fn requires_write(&self) -> bool {
self.0.iter().any(|u| u.write)
}

/// Whether any of the permissions may require read access.
pub fn requires_read(&self) -> bool {
self.0.len() > 0
}

/// Revokes a precise access. Only unlocks matching in scope and type (read or write) exactly
/// are revoked.
pub fn lock(&mut self, access: Access) {
self.0.retain(|u| {
&u.scope != &access.scope || &u.write != &access.write
})
}

/// Adds an unlock.
pub fn unlock(&mut self, spec: UnlockSpec) {
self.0.push(spec.into())
}

/// Revokes any permissions that may require write access.
pub fn coerce_read(&mut self) {
self.0.retain(|u| !u.write);
}
}

impl PermissionModel for Permissions {
impl AccessControl for Permissions {
fn allowed(&self, access: &Access) -> bool {
let t = Instant::now();
for perm in &self.0 {
@@ -162,7 +173,7 @@ impl Unlock {
}
}

impl PermissionModel for Unlock {
impl AccessControl for Unlock {
fn allowed(&self, access: &Access) -> bool {
(self.write || !access.write) && self.scope.allowed(access)
}
@@ -180,7 +191,7 @@ pub enum Scope<'a> {
Global,
}

impl<'a> PermissionModel for Scope<'a> {
impl<'a> AccessControl for Scope<'a> {
fn allowed(&self, access: &Access) -> bool {
match (self, &access.scope) {
(&Scope::Global, _) => true,

+ 9
- 1
goblin-core/src/ser.rs View File

@@ -2,6 +2,7 @@ use hex::{FromHex, ToHex};
use serde::{self, Deserialize, Deserializer, Serialize, Serializer};
use std::time::{Duration, SystemTime, UNIX_EPOCH};

/// Serializes the system time as seconds since the unix epoch.
pub fn ser_time<S>(t: &SystemTime, ser: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
@@ -13,6 +14,7 @@ where
ser.serialize_i64(secs)
}

/// Derializes the system time from seconds since the unix epoch.
pub fn de_time<'a, D>(des: D) -> Result<SystemTime, D::Error>
where
D: Deserializer<'a>,
@@ -24,6 +26,7 @@ where
})
}

/// Serializes an optional duration to seconds.
pub fn ser_opt_dur<S>(d: &Option<Duration>, ser: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
@@ -31,6 +34,7 @@ where
d.map(|d| d.as_secs()).serialize(ser)
}

/// Deserializes an optional duration from seconds.
pub fn de_opt_dur<'a, D>(des: D) -> Result<Option<Duration>, D::Error>
where
D: Deserializer<'a>,
@@ -38,7 +42,7 @@ where
Option::<u64>::deserialize(des).map(|s| s.map(|s| Duration::from_secs(s)))
}

/// Serializes a binary buffer as a hex string.
pub fn as_hex<T, S>(buf: &T, ser: S) -> Result<S::Ok, S::Error>
where
T: AsRef<[u8]>,
@@ -47,6 +51,7 @@ where
ser.serialize_str(&buf.as_ref().to_hex())
}

/// Deserializes a binary buffer from a hex string.
pub fn from_hex<'a, D>(des: D) -> Result<Vec<u8>, D::Error>
where
D: Deserializer<'a>,
@@ -56,6 +61,7 @@ where
})
}

/// Deserializes a n-byte binary buffer from a hex string.
fn from_hexn<'a, D>(des: D, n: usize) -> Result<Vec<u8>, D::Error>
where
D: Deserializer<'a>,
@@ -69,6 +75,7 @@ where
})
}

/// Deserializes a 16-byte binary buffer from a hex string.
pub fn from_hex16<'a, D>(des: D) -> Result<[u8; 16], D::Error>
where
D: Deserializer<'a>,
@@ -78,6 +85,7 @@ where
Ok(r)
}

/// Deserializes a 32-byte binary buffer from a hex string.
pub fn from_hex32<'a, D>(des: D) -> Result<[u8; 32], D::Error>
where
D: Deserializer<'a>,

+ 2
- 2
goblin-core/src/state.rs View File

@@ -4,7 +4,7 @@ use err::{Error, Result};
use format::{Commit, Delta, Vault, VaultMut};
use keyring::{Entry, Keyring, Path, Value};
use location::VaultLocation;
use perm::{Access, PermissionModel, Permissions, Scope, UnlockSpec};
use perm::{Access, AccessControl, Permissions, Scope, UnlockSpec};
use std::borrow::{Borrow, Cow};
use std::collections::HashSet;
use std::io;
@@ -280,7 +280,7 @@ impl VaultState {
}
}

impl PermissionModel for VaultState {
impl AccessControl for VaultState {
fn allowed(&self, access: &Access) -> bool {
self.permissions.allowed(access)
}

Loading…
Cancel
Save