Skip to content

Commit

Permalink
Build static binaries w/ libsodium for release (#5)
Browse files Browse the repository at this point in the history
* Build static binaries w/ libsodium for release

* Add readme content

* Stretch password word count to 6

* Read unseal password from stdin if not provided during launch

* Fix attrs block reading and always canonicalize the input path before continuing

* Fix tests
  • Loading branch information
pirogoeth authored Sep 13, 2018
1 parent f3896fc commit 9d23cde
Show file tree
Hide file tree
Showing 13 changed files with 123 additions and 45 deletions.
16 changes: 10 additions & 6 deletions .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,21 +9,25 @@ matrix:
rust: nightly
env: TARGET=x86_64-unknown-linux-gnu

addons:
apt:
packages:
- clang-3.9
- curl
- libclang-3.9-dev

cache:
cargo: true
directories:
- "$HOME/lib"

before_install:
- if [ "$TRAVIS_OS_NAME" == "osx" ]; then brew install libsodium; fi
- if [ "$TRAVIS_OS_NAME" == "linux" ]; then source ./ci/linux-libsodium-path.sh; fi

install:
- if [ "$TRAVIS_OS_NAME" == "linux" ]; then bash ./ci/linux-build-libsodium.sh; fi
- source ./ci/libsodium-build.sh
- source ./ci/libsodium-env.sh

script:
- cargo build --verbose --target "${TARGET}"
- cargo test --verbose --target "${TARGET}"
- cargo test --verbose

before_deploy:
- cargo build --release --verbose --target "${TARGET}"
Expand Down
12 changes: 12 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ libflate = "0.1.0"
log = "0.4.0"
quicli = "0.3.0"
rand = "0.5.5"
rpassword = "2.0.0"
sodiumoxide = "0.1.0"
spinners = "1.0.0"
structopt = "0.2.10"
Expand Down
47 changes: 42 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,13 +1,50 @@
# sneakercopy

## Requirements
A tool for creating encrypted archives for handling sensitive content.

- [libsodium](https://github.com/jedisct1/libsodium)
- macOS: `brew install libsodium`
- Linux: Tested down to libsodium v1.0.11 (`libsodium18` on Debian 9.5)
Sneakercopy stands on the shoulders of giants such as [tar],
[sodiumoxide] / [libsodium], and [libflate] to pack, compress,
and encrypt sensitive files into a light container called a "tarbox".

We use the system defined in [RFC2289] to generate short, memorable,
easily writable passwords. `libsodium`'s `scryptsalsa208sha256` is used to derive
a hash to encrypt the compressed data stream with.

[tar]: https://crates.io/crates/tar
[sodiumoxide]: https://crates.io/crates/sodiumoxide
[libsodium]: https://github.com/jedisct1/libsodium
[libflate]: https://crates.io/crates/libflate
[RFC2289]: https://tools.ietf.org/html/rfc2289

## Usage

### Seal a file/directory

```
# Creates `directory.tarbox` in the current directory
λ sneakercopy seal /path/to/directory
⢀⠀ Packing...
secret: FOWL-BON-MEMO-ROSY-HORN
# Creates `configs.tarbox` in `/var/backups`
λ sneakercopy seal -o /var/backups/configs.tarbox /etc
⢀⠀ Packing...
secret: ROAD-SHIN-TAKE-OLDY-YANK
```

### Unseal a tarbox

```
# Unseals the contents of `directory.tarbox` into current directory
λ sneakercopy unseal ./directory.tarbox FOWL-BON-MEMO-ROSY-HORN
# Unseals the contents of `configs.tarbox` into `/etc`
λ sneakercopy unseal -C /etc/ /var/backups/configs.tarbox ROAD-SHIN-TAKE-OLDY-YANK
```

## Compiling

- Install `libsodium`
- Use `./ci/libsodium-build.sh` to prepare a static `libsodium` installation
- Set up build flags with `./ci/libsodium-env.sh`
- `cargo build`
- Done!
16 changes: 16 additions & 0 deletions ci/libsodium-build.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
#!/usr/bin/env bash

LIBSODIUM_VERSION=${LIBSODIUM_VERSION:-1.0.16}

mkdir -p $HOME/lib/libsodium
curl -sSL -olibsodium.tar.gz https://github.com/jedisct1/libsodium/releases/download/${LIBSODIUM_VERSION}/libsodium-${LIBSODIUM_VERSION}.tar.gz
tar xvfz libsodium.tar.gz --strip-components 1 -C $HOME/lib/libsodium
pushd $HOME/lib/libsodium && \
./configure \
--prefix=$HOME/lib/libsodium \
--disable-debug \
--disable-dependency-tracking \
--disable-shared && \
make && \
make install && \
popd
8 changes: 8 additions & 0 deletions ci/libsodium-env.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
#!/usr/bin/env bash

export PKG_CONFIG_PATH=$HOME/lib/libsodium/lib/pkgconfig:$PKG_CONFIG_PATH
export LD_LIBRARY_PATH=$HOME/lib/libsodium/lib:$LD_LIBRARY_PATH

export SODIUM_STATIC=true
export SODIUM_LIB_DIR=$HOME/lib/libsodium/src/libsodium/.libs
export SODIUM_INC_DIR=$HOME/lib/libsodium/src/libsodium/include
6 changes: 0 additions & 6 deletions ci/linux-build-libsodium.sh

This file was deleted.

2 changes: 0 additions & 2 deletions ci/linux-libsodium-path.sh

This file was deleted.

16 changes: 11 additions & 5 deletions src/bin/sneakercopy.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
#![recursion_limit = "1024"]
#![feature(try_from)]

extern crate rpassword;
#[macro_use]
extern crate quicli;
extern crate sneakercopy;
Expand Down Expand Up @@ -51,7 +52,7 @@ enum Subcommand {
path: PathBuf,

#[structopt(help = "Password used for encryption")]
password: String,
password: Option<String>,

#[structopt(
short = "C",
Expand Down Expand Up @@ -87,12 +88,12 @@ fn entrypoint(args: Cli) -> sneakercopy::errors::Result<()> {
path,
output,
force,
} => seal_subcmd(&args, path, output, force)?,
} => seal_subcmd(&args, &path.canonicalize().unwrap(), output, force)?,
Subcommand::Unseal {
path,
password,
dest,
} => unseal_subcmd(&args, path, dest, password)?,
} => unseal_subcmd(&args, &path.canonicalize().unwrap(), dest, password)?,
}

Ok(())
Expand Down Expand Up @@ -127,12 +128,17 @@ fn unseal_subcmd(
_args: &Cli,
path: &PathBuf,
dest: &Option<PathBuf>,
password: &String,
password: &Option<String>,
) -> sneakercopy::errors::Result<()> {
check_path(&path)?;

let password = password.clone().unwrap_or_else(|| {
return rpassword::prompt_password_stdout("secret: ")
.expect("can't open tarbox without a secret!");
});

let sb = tarbox::TarboxSecretBuilder::new();
let sb = sb.password(password.clone());
let sb = sb.password(password);
let dest = dest.clone().unwrap_or(path.parent().unwrap().to_path_buf());

unseal_path(&path, &dest, sb)?;
Expand Down
4 changes: 3 additions & 1 deletion src/password.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
use rand::{prng, seq, thread_rng, SeedableRng};

const PASSWORD_WORD_COUNT: usize = 6;

// generate a reasonable password
pub fn generate_password() -> String {
let mut rng = prng::chacha::ChaChaRng::from_rng(thread_rng()).unwrap();
let sample = seq::sample_iter(&mut rng, WORDS.into_iter(), 5).unwrap();
let sample = seq::sample_iter(&mut rng, WORDS.into_iter(), PASSWORD_WORD_COUNT).unwrap();
sample
.into_iter()
.map(|x| String::from(*x))
Expand Down
21 changes: 13 additions & 8 deletions src/tarbox/attributes.rs
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,10 @@ impl Attributes {
VERSION
}

pub fn attr_block_size() -> usize {
(NONCEBYTES + SALTBYTES) as usize
}

pub fn nonce(&self) -> &NonceBytes {
&self.nonce
}
Expand All @@ -42,17 +46,18 @@ impl Attributes {
}

pub fn from_bytes(source: Vec<u8>) -> errors::Result<Attributes> {
let expected: usize = NONCEBYTES + SALTBYTES;
let actual: usize = source.len();
if actual > expected {
bail!(errors::ErrorKind::SourceTooLarge(expected, actual));
}

let mut nonce = [0; NONCEBYTES];
nonce.copy_from_slice(&source[..NONCEBYTES]);

let mut salt = [0; SALTBYTES];
salt.copy_from_slice(&source[NONCEBYTES..NONCEBYTES + SALTBYTES]);

let remaining = source.len() - (NONCEBYTES + SALTBYTES);
if remaining > 0 {
bail!(errors::ErrorKind::SourceNotFullyDrained(remaining));
}

Ok(Attributes::new(nonce, salt))
}

Expand Down Expand Up @@ -131,10 +136,10 @@ mod tests {
let res = Attributes::from_bytes(source);
assert!(res.is_err());
let err = res.unwrap_err();
if let errors::Error(errors::ErrorKind::SourceNotFullyDrained(num), _) = err {
if let errors::Error(errors::ErrorKind::SourceTooLarge(_, actual), _) = err {
assert_eq!(
2, num,
"only 2 bytes were expected to be remaining (undrained)"
58, actual,
"only 56 bytes were expected in attrs source (2 undrained)"
);
} else {
panic!(format!(
Expand Down
13 changes: 4 additions & 9 deletions src/tarbox/decoder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -36,14 +36,9 @@ impl Decoder {

// Now that we have verified the version of the header,
// read all bytes until a `NUL` is encountered.
let attrs_data: Vec<_> = inner
.clone()
.into_iter()
.take_while(|b| *b != 0x0)
.collect();
let attrs_data: Vec<_> = inner.drain(..Attributes::attr_block_size()).collect();

let attrs = Attributes::from_bytes(attrs_data.clone())?;
inner.drain(..attrs_data.len());
let attrs = Attributes::from_bytes(attrs_data)?;

// The next byte we read should be a `NUL`.
let next = inner.remove(0);
Expand Down Expand Up @@ -154,8 +149,8 @@ mod tests {

assert!(res.is_err());
let err = res.unwrap_err();
if let errors::Error(errors::ErrorKind::SourceNotFullyDrained(num), _) = err {
assert_eq!(2, num, "expected undrained data to be length of inner file");
if let errors::Error(errors::ErrorKind::ExpectedNullByte(actual), _) = err {
assert_eq!(0xfa, actual, "expected first byte of payload, not {}", actual);
} else {
panic!(format!(
"expected `SourceNotFullyDrained` error, got: {:?}",
Expand Down
6 changes: 3 additions & 3 deletions src/tarbox/errors.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,9 @@ error_chain! {
display("invalid key data: {}", kd),
}

SourceNotFullyDrained(size: usize) {
description("source vector was not fully drained"),
display("source vector was not fully drained: {} elements remaining", size),
SourceTooLarge(expected: usize, actual: usize) {
description("source vector is too large"),
display("source vector is too large: {} expected < {} actual", expected, actual),
}

VersionMismatch(expected: u8, actual: u8) {
Expand Down

0 comments on commit 9d23cde

Please # to comment.