commit 8597b4b6ed4b620136c81985b55e90ee30719665
parent 5642f100cb40e011b4805229773df0d3153691a9
Author: lash <dev@holbrook.no>
Date: Sun, 17 Jul 2022 10:55:55 +0000
Add documentation
Diffstat:
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 => {