wala-rust

Unnamed repository; edit this file 'description' to name the repository.
Info | Log | Files | Refs | README | LICENSE

commit 8597b4b6ed4b620136c81985b55e90ee30719665
parent 5642f100cb40e011b4805229773df0d3153691a9
Author: lash <dev@holbrook.no>
Date:   Sun, 17 Jul 2022 10:55:55 +0000

Add documentation

Diffstat:
Msrc/auth/mock.rs | 12++++++++++++
Msrc/auth/mod.rs | 88+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Msrc/auth/pgp.rs | 21+++++++++++++++++++++
Msrc/main.rs | 45++++++---------------------------------------
Msrc/meta.rs | 36++++++++++++++++++++++++++++++++++++
Msrc/record.rs | 40+++++++++++++++++++++++++++++++++++++++-
Msrc/request.rs | 10++++++++++
7 files changed, 212 insertions(+), 40 deletions(-)

diff --git a/src/auth/mock.rs b/src/auth/mock.rs @@ -1,3 +1,8 @@ +//! The `mock` auth module is provided to facilitate testing. +//! +//! If active, it will be executed for the `mock` authentication scheme. +//! +//! Under the `mock` scheme, a valid signature is simply the same value as the identity key. use std::io::{ Read, }; @@ -8,6 +13,13 @@ use crate::auth::{ }; +/// Verifies the given [auth::AuthSpec](crate::auth::AuthSpec) structure against the `mock` scheme. +/// +/// # Arguments +/// +/// * `auth` - Authentication data submitted by client. +/// * `data` - Content body submitted by client, to match signature against. +/// * `data_length` - Length of content body pub fn auth_check(auth: &AuthSpec, data: impl Read, data_length: usize) -> Result<AuthResult, AuthError> { if auth.method != "mock" { return Err(AuthError{}); diff --git a/src/auth/mod.rs b/src/auth/mod.rs @@ -1,29 +1,115 @@ +//! Using HTTP Authentication, a mutable reference can be generated to mutable content. +//! +//! The mutable reference is generated from the identity value of the authenticating client, +//! together with an identifier, which can be any arbitrary byte value. +//! +//! Mutable references are generated using [record::ResourceKey](record::ResourceKey) together with +//! the [auth::AuthResult](auth::AuthResult) struct. +//! +//! # How to authenticate +//! +//! Authentication in `wala` uses the `Authorization` HTTP header with the custom `PUBSIG` scheme +//! to determine the identity for which a client wishes to generate a mutable reference. The header +//! uses the following format: +//! +//! ``` +//! Authorization: PUBSIG <scheme>:<identity>:<signature> +//! ``` +//! +//! In the above, `scheme` specifies the authentication module to use (submodules of +//! [wala::auth](crate::auth). `identity` is the key against which the `signature` will be +//! validated. +//! +//! There is no access control for which key may store mutable references. All that is required is +//! a valid signature. +//! +//! # Mutable reference +//! +//! The generated mutable reference is a digest of the `identity` from the authentication, and the +//! local part of the `URL`. +//! +//! For example, given the request: +//! +//! ``` +//! PUT /xyzzy HTTP/1.1 +//! Authorization: PUBSIG foo:123:456 +//! Content-Length: 3 +//! +//! bar +//! ``` +//! +//! If we pretend that `456` is a valid signature for the `123` under the fictional `foo` +//! authentication scheme, then the mutable reference generated will be `SHA256("xyzzy" | "123")` +//! which is `266e6c9060785c64b652cb5aea3a99f0ef019366372ced42ea9db25877288eed`. +//! +//! The immutable reference (generated from the content body "bar") will simultaneously be stored, +//! under `SHA256("bar")`, which is `fcde2b2edba56bf408601fb721fe9b5c338d10ee429ea04fae5511b68fbf8fb9`. +//! +//! Consequtively, for a `wala` server running on `localhost:8000`, the content can be retrieved using +//! both of the following `URLs`: +//! +//! ``` +//! http://localhost:8000/266e6c9060785c64b652cb5aea3a99f0ef019366372ced42ea9db25877288eed +//! http://localhost:8000/fcde2b2edba56bf408601fb721fe9b5c338d10ee429ea04fae5511b68fbf8fb9 +//! ``` +//! +//! # Overwriting a reference +//! +//! If a subsequent mutable reference is generated for different content, then the existing mutable +//! reference will be overwritten. `wala` provides no feature to write-protect existing mutable +//! references. +//! +//! Of course, for immutable references, the reference for the same content will always be the +//! same. +//! +//! # Authentication schemes +//! +//! Every submodule of [wala::auth](crate::auth) defines individual authentication schemes. +//! +//! All schemes, even the [mock](crate::auth::mock) one, must be explicitly be included as a +//! feature during build. +//! +//! For any scheme included during build, a module function `auth_check` will be called to verify +//! the data. See [auth::mock::auth_check](crate::auth::mock::auth_check) for an example. +//! +//! Details on input formats for each scheme is documented within the modules themselves. use std::str::FromStr; use std::error::Error; use std::fmt; +/// Holds the result of a client authentication request. pub struct AuthResult { + /// The resolved identity value. An unsuccessful authentication will result in an empty vector. pub identity: Vec<u8>, + /// If true, authentication verification has been attempted and failed. pub error: bool, } +/// Encapsulates the input provided by the client for authentication. pub struct AuthSpec { + /// Authentication method. This determines which authentication submodule will be used. pub method: String, + /// The key corrdsponding to the signature. pub key: String, + /// Signature over the content of the request. The signature must match against the given key. pub signature: String, } impl AuthSpec { + /// Resturns true if the `signature` matches the `key` using the given `method` for the + /// `auth::AuthSpec`. pub fn valid(&self) -> bool { self.key.len() > 0 } } impl AuthResult { + /// True if authentication has been successfully executed. pub fn active(&self) -> bool { self.identity.len() > 0 } + /// True if no error occurred during verification. Also returns true if no verification has been attmpted. pub fn valid(&self) -> bool { !self.error } @@ -36,6 +122,7 @@ impl fmt::Debug for AuthResult { } #[derive(Debug)] +/// Indicates invalid authentication data from client. pub struct AuthSpecError; impl Error for AuthSpecError { @@ -104,6 +191,7 @@ impl fmt::Debug for AuthSpec { } #[derive(Debug)] +/// Error type indicating that an error has occurred during authentication. pub struct AuthError; impl fmt::Display for AuthError { diff --git a/src/auth/pgp.rs b/src/auth/pgp.rs @@ -1,3 +1,14 @@ +//! This module provides authentication using PGP signatures. +//! +//! The public key and signature may be provided both as literal values from the individual PGP packets (i.e. raw public key and signature), +//! or as the conventional packet bundle. +//! +//! If using bundle, the encoded data must be from the binary content, e.g. the output value of: +//! +//! ``` +//! gpg -b <file> +//! ``` +//! use std::io::Read; use crate::auth::{ AuthSpec, @@ -102,6 +113,16 @@ fn check_sig_bundle(public_key: &PublicKey, signature_data: Vec<u8>, mut message false } +/// Verifies the given [auth::AuthSpec](crate::auth::AuthSpec) structure against the `pgp` scheme. +/// +/// The `key` and `signature` fields of the [auth::AuthSpec](crate::auth::AuthSpec) **MUST** be +/// base64 encoded. +/// +/// # Arguments +/// +/// * `auth` - Authentication data submitted by client. +/// * `data` - Content body submitted by client, to match signature against. +/// * `data_length` - Length of content body. pub fn auth_check(auth: &AuthSpec, data: impl Read, data_length: usize) -> Result<AuthResult, AuthError> { if auth.method != "pgp" { return Err(AuthError{}); diff --git a/src/main.rs b/src/main.rs @@ -1,32 +1,5 @@ #![crate_name = "wala"] -//! wala is a content-adressed HTTP server. -//! When content is uploaded, the URL to the content is automatcally generated from the contents of the request body. The URL will always be the same for the same content. -//! These will be referred to as _immutable references_. -//! -//! The content of the URL is the SHA256 hash of the content, in hex, lowercase, without a 0x -//! prefix. -//! -//! The wala daemon will listen to all ip addresses on port 8000 by default, aswell as store and -//! serve uploaded files from the current directory. This behavior can be modified by the argument -//! options. See `cargo run -- --help` for details. -//! -//! Content is stored by making `PUT` requests to the server. With a server running on -//! `localhost:8000`a `PUT` with the content body `foo` can in turn be retrieved at: -//! -//! ``` -//! http://localhost:8000/2c26b46b68ffc68ff99b453c1d30413413422d706483bfa0f98a5e886266e7ae -//! ``` -//! If built with the `meta` feature, the content type specified in the `PUT` will be stored and used -//! when the file is retrieved. `wala` will _not_ double-check the content type against the actual -//! content. -//! -//! wala also provides a way to generate URL aliases to content based on cryptographic identities. -//! These will be referred to as _mutable references_. -//! This is accomplished using a custom `PUBSIG` scheme for the `Authorization` header, specifying -//! the cryptographic engine to use, the public key and the signature of the content. See the -//! [wala::auth](crate::auth) module for more details. -//! use tiny_http::{ Server, @@ -55,20 +28,17 @@ use std::io::{ use env_logger; -mod auth; -use auth::{ +use wala::auth::{ AuthSpec, AuthResult, }; -mod record; -use record::{ +use wala::record::{ RequestResult, RequestResultType, }; -mod request; -use request::process_method; +use wala::request::process_method; mod arg; use arg::Settings; @@ -77,15 +47,12 @@ use log::{info, error}; use tempfile::tempfile; - #[cfg(feature = "dev")] -use crate::auth::mock::auth_check as mock_auth_check; +use wala::auth::mock::auth_check as mock_auth_check; #[cfg(feature = "pgpauth")] -use crate::auth::pgp::auth_check as pgp_auth_check; +use wala::auth::pgp::auth_check as pgp_auth_check; -#[cfg(feature = "meta")] -mod meta; #[derive(Debug)] pub struct NoAuthError; @@ -277,7 +244,7 @@ fn process_meta(req: &Request, path: &Path, digest: Vec<u8>) -> Option<Mime> { #[cfg(feature = "meta")] match m { Some(v) => { - match meta::register_type(path, digest, v) { + match wala::meta::register_type(path, digest, v) { Err(e) => { error!("could not register content type: {}", &e); }, diff --git a/src/meta.rs b/src/meta.rs @@ -1,3 +1,25 @@ +//! The `meta` module is an optional feature which stores the MIME type value from the +//! `Content-Type` header of a client `PUT` request. +//! +//! The MIME type is stored on the server under the same file identifier as the content but with a +//! postfix '.meta'. +//! +//! A check is performed to validate that the specified value is a valid MIME type string. However, +//! no further check is performed to attempt to verify whether the declared MIME type correctly +//! describes the file contents. +//! +//! For subsequent `GET` requests for the same content, the stored MIME type will be used as the +//! `Content-Type` header. +//! +//! If no MIME type was specified for the content, or if the feature is not enabled, the +//! `Content-Type` header will always be `application/octet-stream` +//! +//! Any subsequent `PUT` for the same content specifying a `Content-Type` header will _overwrite_ +//! the previously stored MIME type. +//! +//! There is no feature to _delete_ the MIME type association from the server. However, setting the MIME +//! type explicitly to `application/octet-stream` will achieve the same result as for records that +//! do not have a MIME type association. use std::fs::{ File, read, @@ -30,6 +52,14 @@ fn meta_path(path: &Path, digest: Vec<u8>) -> Result<PathBuf, std::io::Error> { Ok(path_canon) } + +/// Set a MIME type for the specified content. +/// +/// # Arguments +/// +/// * `path` - Absolute path to storage diectory. +/// * `digest` - Immutable reference to content. +/// * `typ` - MIME type to store for the content. pub fn register_type(path: &Path, digest: Vec<u8>, typ: Mime) -> Result<(), std::io::Error> { match meta_path(path, digest) { Ok(v) => { @@ -47,6 +77,12 @@ pub fn register_type(path: &Path, digest: Vec<u8>, typ: Mime) -> Result<(), std: Ok(()) } +/// Retrieve the MIME type for the specified content. +/// +/// # Arguments +/// +/// * `path` - Absolute path to storage diectory. +/// * `digest` - Immutable reference to content. pub fn get_type(path: &Path, digest: Vec<u8>) -> Option<Mime> { let digest_hex = hex::encode(&digest); match meta_path(path, digest) { diff --git a/src/record.rs b/src/record.rs @@ -25,20 +25,33 @@ use mime::Mime; use log::{debug, info, error}; #[derive(Debug, PartialEq)] +/// Status codes to represent the result of a request. pub enum RequestResultType { + /// Record has been found. Found, + /// Record has been updated or created. Changed, + /// Cannot find and/or read record. ReadError, + /// Cannot write immutable record to storage. WriteError, + /// Authentication cannot be verified (signature mismatch). AuthError, + /// Invalid request from client. InputError, + /// Cannot store mutable record. RecordError, } +/// Interface to interpret and read the result of a request. pub struct RequestResult { + /// Result code of the request. pub typ: RequestResultType, + /// Contains the result body (reference string) of a PUT request. pub v: Option<String>, + /// Contains the result body (as a reader) of a GET request. pub f: Option<File>, + /// Contains the MIME type of the content of a GET response (if build with the `meta` feature). pub m: Option<Mime>, } @@ -68,11 +81,15 @@ impl Error for RequestResult { } } +/// Represents a single record on the server. pub struct Record { + /// Digest of content. pub digest: Vec<u8>, + /// Server side path to content. pub path: PathBuf, } +/// Identifier part of the for mutable content reference. pub struct ResourceKey { v: Vec<u8>, } @@ -97,6 +114,8 @@ impl fmt::Display for ResourceKey { } impl ResourceKey { + /// Create a reference for the identifier using the verified identity from client + /// authentication. pub fn pointer_for(&self, subject: &AuthResult) -> Vec<u8> { let mut h = Sha256::new(); debug!("update {:?} {:?}", hex::encode(&self.v), hex::encode(&subject.identity)); @@ -107,6 +126,13 @@ impl ResourceKey { } +/// Store an immutable record on file. +/// +/// # Arguments +/// +/// * `path` - Absolute path to storage directory. +/// * `f` - Reader providing the contents of the file. +/// * `expected_size` - Size hint of content. pub fn put_immutable(path: &Path, mut f: impl Read, expected_size: usize) -> Result<Record, RequestResult> { let z: Vec<u8>; let hash: String; @@ -179,6 +205,11 @@ pub fn put_immutable(path: &Path, mut f: impl Read, expected_size: usize) -> Res Ok(r) } +/// Store an immutable record on file with a mutable reference. +/// +/// # Arguments +/// +/// TODO: use resourcekey instead of pointer here pub fn put_mutable(pointer: Vec<u8>, path: &Path, mut f: impl Read, expected_size: usize) -> Result<Record, RequestResult> { let mutable_ref = hex::encode(&pointer); let link_path_buf = path.join(&mutable_ref); @@ -199,7 +230,14 @@ pub fn put_mutable(pointer: Vec<u8>, path: &Path, mut f: impl Read, expected_siz } } -pub fn get(pointer: Vec<u8>, path: &Path) -> Option<File> { //{ impl Read> { + +/// Retrieve the content for a single record. +/// +/// # Arguments +/// +/// * `pointer` - A reference to the pointer. +/// * `path` - Absolute path to storage directory. +pub fn get(pointer: Vec<u8>, path: &Path) -> Option<File> { let path_canon = match path.canonicalize() { Ok(v) => { v diff --git a/src/request.rs b/src/request.rs @@ -27,6 +27,16 @@ use log::{ error, }; +/// Handle client input by method type. +/// +/// # Arguments +/// +/// * `method` - The HTTP method of the client request. +/// * `url` - The local part of the URL of the client request. +/// * `f` - Reader providing the content body of a client PUT request. +/// * `expected_size` - Size hint for content body. +/// * `path` - Absolute path to storage directory. +/// * `auth_result` - Result of authentication (if any) the client has provided with the request. pub fn process_method(method: &Method, url: String, mut f: impl Read, expected_size: usize, path: &Path, auth_result: AuthResult) -> RequestResult { match method { Method::Put => {