GithubHelp home page GithubHelp logo

stouset / secrets Goto Github PK

View Code? Open in Web Editor NEW
199.0 10.0 9.0 353 KB

Secure storage for cryptographic secrets in Rust

License: Other

Rust 98.76% Dockerfile 1.24%
rust cryptographic-secrets cryptography-tools memory-management

secrets's Introduction

secrets

Build Status

Cargo Crate Docs License

secrets is a library to help Rust programmers safely held cryptographic secrets in memory.

It is mostly an ergonomic wrapper around the memory-protection utilities provided by libsodium.

Fixed-size buffers allocated on the stack gain the following protections:

  • mlock(2) is called on the underlying memory
  • the underlying memory is zeroed out when no longer in use
  • they are borrowed for their entire lifespan, so cannot be moved
  • they are compared in constant time
  • they are prevented from being printed by Debug
  • they are prevented from being Cloned

Fixed and variable-sized buffers can be allocated on the heap and gain the following protections:

  • the underlying memory is protected from being read from or written to with mprotect(2) unless an active borrow is in scope
  • mlock(2) is called on the allocated memory
  • the underlying memory is zeroed out when no longer in use
  • overflows and underflows are detected using inaccessible guard pages, causing an immediate segmentation fault and program termination
  • short underflows that write to memory are detected when memory is freed using canaries, and will result in a segmentation fault and program termination

Panic Safety

This library is explicitly not panic-safe. To ensure the safety of protected memory space, this library can and will panic if it is unable to enforce its advertised guarantees.

Similarly, this library will cause segmentation faults if (and only if) it detects certain safety violations. For example, this can happen if a process attempts to directly read or write to the contents of memory that hasn't been properly unlocked, or if canaries have been overwritten. This library has been written to ensure that such violations should be impossible to cause through well-formed Rust, and so should only occur as a result of a security vulnerability.

Examples

Example: generating cryptographic keys

Secret::<[u8; 16]>::random(|s| {
    // use `s` as if it were a `&mut [u8; 16]`
    //
    // the memory is `mlock(2)`ed and will be zeroed when this closure
    // exits
});

Example: load a master key from disk and generate subkeys from it

use std::fs::File;
use std::io::Read;

use libsodium_sys as sodium;
use secrets::SecretBox;

const KEY_LEN : usize = sodium::crypto_kdf_KEYBYTES     as _;
const CTX_LEN : usize = sodium::crypto_kdf_CONTEXTBYTES as _;

const CONTEXT : &[u8; CTX_LEN] = b"example\0";

fn derive_subkey(
    key:       &[u8; KEY_LEN],
    context:   &[u8; CTX_LEN],
    subkey_id: u64,
    subkey:    &mut [u8],
) {
    unsafe {
        libsodium_sys::crypto_kdf_derive_from_key(
            subkey.as_mut_ptr(),
            subkey.len(),
            subkey_id,
            context.as_ptr().cast(),
            key.as_ptr()
        );
    }
}

let master_key = SecretBox::<[u8; KEY_LEN]>::try_new(|mut s| {
    File::open("example/master_key/key")?.read_exact(s)
})?;

let subkey_0 = SecretBox::<[u8; 16]>::new(|mut s| {
    derive_subkey(&master_key.borrow(), CONTEXT, 0, s);
});

let subkey_1 = SecretBox::<[u8; 16]>::new(|mut s| {
    derive_subkey(&master_key.borrow(), CONTEXT, 1, s);
});

assert_ne!(
    subkey_0.borrow(),
    subkey_1.borrow(),
);

Example: securely storing a decrypted ciphertext in memory

use std::fs::File;
use std::io::Read;

use libsodium_sys as sodium;
use secrets::{SecretBox, SecretVec};

const KEY_LEN   : usize = sodium::crypto_secretbox_KEYBYTES   as _;
const NONCE_LEN : usize = sodium::crypto_secretbox_NONCEBYTES as _;
const MAC_LEN   : usize = sodium::crypto_secretbox_MACBYTES   as _;

let mut key        = SecretBox::<[u8; KEY_LEN]>::zero();
let mut nonce      = [0; NONCE_LEN];
let mut ciphertext = Vec::new();

File::open("example/decrypted_ciphertext/key")?
    .read_exact(key.borrow_mut().as_mut())?;

File::open("example/decrypted_ciphertext/nonce")?
    .read_exact(&mut nonce)?;

File::open("example/decrypted_ciphertext/ciphertext")?
    .read_to_end(&mut ciphertext)?;

let plaintext = SecretVec::<u8>::new(ciphertext.len() - MAC_LEN, |mut s| {
    if -1 == unsafe {
        sodium::crypto_secretbox_open_easy(
            s.as_mut_ptr(),
            ciphertext.as_ptr(),
            ciphertext.len() as _,
            nonce.as_ptr(),
            key.borrow().as_ptr(),
        )
    } {
        panic!("failed to authenticate ciphertext during decryption");
    }
});

assert_eq!(
    *b"attack at dawn",
    *plaintext.borrow(),
);

License

Licensed under either of

at your option.

secrets's People

Contributors

aochagavia avatar emberian avatar ethindp avatar oblique avatar stouset avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

secrets's Issues

`bool` and `char`'s `Bytes` implementations cause undefined behavior.

The documentation for secrets::traits::Bytes states:

Any type that implements Bytes must not exhibit undefined behavior when its underlying bits are set to any arbitrary bit pattern.

Currently, bool and char (the primitive types) have implementations for Bytes (https://github.com/stouset/secrets/blob/master/src/traits.rs#L69 ), but these types can not be set to arbitrary bit patterns (specifically, bool must have the bit pattern 0x00 or 0x01, and char must have a bit pattern in the range 0x0000_0000..=0x0000_D7FF or the range 0x0000_E000..=0x0010_FFFF)

The following example program exhibits undefined behavior due to this (run it in debug mode and in release mode and you'll most likely see different results):

fn main() {
    let b: char = secrets::traits::Bytes::uninitialized();
    match b {
        // Note that these two patterns together include all valid char values
        '\x00'..='\u{10fffe}' => dbg!("char1"),
        '\x01'..='\u{10ffff}' => dbg!("char2"), // This prints in release mode on my machine (secrets 1.2.0, rustc 1.61.0)
        _ => dbg!("huh?"), // This prints in debug mode on my machine
    };
}

How do I create a SecretVec which length is unknown?

In this example I want to encrypt a text that is stored on a file (I tried first to have it provided by stdin but I couldn't make it work).

use std::fs::File;
use std::io::Read;

use libsodium_sys as sodium;
use secrets::{SecretBox, SecretVec};

const KEY_LEN   : usize = sodium::crypto_secretbox_KEYBYTES   as _;
const NONCE_LEN : usize = sodium::crypto_secretbox_NONCEBYTES as _;
const MAC_LEN   : usize = sodium::crypto_secretbox_MACBYTES   as _;

pub fn get_ciphertext(key_path: &str, nonce_path: &str, text_path: &str) -> std::io::Result<SecretVec::<u8>>{
    let mut key        = SecretBox::<[u8; KEY_LEN]>::zero();
    let mut nonce      = [0; NONCE_LEN];
    let mut plaintext = SecretVec::zero(14);
    

    File::open(key_path)?
        .read_exact(key.borrow_mut().as_mut())?;

    File::open(nonce_path)?
        .read_exact(&mut nonce)?;

    File::open(text_path)?
        .read_exact(plaintext.borrow_mut().as_mut())?;

    Ok(SecretVec::<u8>::new(plaintext.len() - MAC_LEN, |s| {
        if -1 == unsafe {
            sodium::crypto_secretbox_easy(
                s.as_mut_ptr(),
                plaintext.borrow().as_ptr(),
                plaintext.len() as _,
                nonce.as_ptr(),
                key.borrow().as_ptr(),
            )
        } {
            panic!("failed");
        }
    }))
}

This is like the decrypt example but using the sodium::crypto_secretbox_easy instead.

It gives the error thread 'main' panicked at 'attempt to subtract with overflow' probably because of the size given by plaintext.len() - MAC_LEN.

Have you any idea on how to fix this?

Question about linkage

Is there any reason that you explicitly search with pkg-config for libsodium?
This is done by libsodium-sys with use-pkg-config feature flag.

I believe this should be left to the user of the crate. Is it ok with you if I remove the link in build.rs and add use-pkg-config feature flag that enables libsodium-sys/use-pkg-config?

Relicense under dual MIT/Apache-2.0

This issue was automatically generated. Feel free to close without ceremony if
you do not agree with re-licensing or if it is not possible for other reasons.
Respond to @cmr with any questions or concerns, or pop over to
#rust-offtopic on IRC to discuss.

You're receiving this because someone (perhaps the project maintainer)
published a crates.io package with the license as "MIT" xor "Apache-2.0" and
the repository field pointing here.

TL;DR the Rust ecosystem is largely Apache-2.0. Being available under that
license is good for interoperation. The MIT license as an add-on can be nice
for GPLv2 projects to use your code.

Why?

The MIT license requires reproducing countless copies of the same copyright
header with different names in the copyright field, for every MIT library in
use. The Apache license does not have this drawback. However, this is not the
primary motivation for me creating these issues. The Apache license also has
protections from patent trolls and an explicit contribution licensing clause.
However, the Apache license is incompatible with GPLv2. This is why Rust is
dual-licensed as MIT/Apache (the "primary" license being Apache, MIT only for
GPLv2 compat), and doing so would be wise for this project. This also makes
this crate suitable for inclusion and unrestricted sharing in the Rust
standard distribution and other projects using dual MIT/Apache, such as my
personal ulterior motive, the Robigalia project.

Some ask, "Does this really apply to binary redistributions? Does MIT really
require reproducing the whole thing?" I'm not a lawyer, and I can't give legal
advice, but some Google Android apps include open source attributions using
this interpretation. Others also agree with
it
.
But, again, the copyright notice redistribution is not the primary motivation
for the dual-licensing. It's stronger protections to licensees and better
interoperation with the wider Rust ecosystem.

How?

To do this, get explicit approval from each contributor of copyrightable work
(as not all contributions qualify for copyright, due to not being a "creative
work", e.g. a typo fix) and then add the following to your README:

## License

Licensed under either of

 * Apache License, Version 2.0, ([LICENSE-APACHE](LICENSE-APACHE) or http://www.apache.org/licenses/LICENSE-2.0)
 * MIT license ([LICENSE-MIT](LICENSE-MIT) or http://opensource.org/licenses/MIT)

at your option.

### Contribution

Unless you explicitly state otherwise, any contribution intentionally submitted
for inclusion in the work by you, as defined in the Apache-2.0 license, shall be dual licensed as above, without any
additional terms or conditions.

and in your license headers, if you have them, use the following boilerplate
(based on that used in Rust):

// Copyright 2016 secrets Developers
//
// Licensed under the Apache License, Version 2.0, <LICENSE-APACHE or
// http://apache.org/licenses/LICENSE-2.0> or the MIT license <LICENSE-MIT or
// http://opensource.org/licenses/MIT>, at your option. This file may not be
// copied, modified, or distributed except according to those terms.

It's commonly asked whether license headers are required. I'm not comfortable
making an official recommendation either way, but the Apache license
recommends it in their appendix on how to use the license.

Be sure to add the relevant LICENSE-{MIT,APACHE} files. You can copy these
from the Rust repo for a plain-text
version.

And don't forget to update the license metadata in your Cargo.toml to:

license = "MIT OR Apache-2.0"

I'll be going through projects which agree to be relicensed and have approval
by the necessary contributors and doing this changes, so feel free to leave
the heavy lifting to me!

Contributor checkoff

To agree to relicensing, comment with :

I license past and future contributions under the dual MIT/Apache-2.0 license, allowing licensees to chose either at their option.

Or, if you're a contributor, you can check the box in this repo next to your
name. My scripts will pick this exact phrase up and check your checkbox, but
I'll come through and manually review this issue later as well.

any updates on using libsodium mshild

FWIW, libsodium recently added an mshield function that performs in-memory encryption. When that is stabilized and released, I'll be able to use it.

Of course, anyone with similar privileges can still find the key and IV in memory and decrypt the secret, but it does increase the level of effort for such an attach.

Originally posted by @stouset in #72 (comment)

That was the result of an other issue from last year, is that still on the roaster?

Trouble compiling on OpenBSD

I'm having trouble compiling on OpenBSD 6.6 with libsodioum (version 1.0.18) and rustc version 1.38.0.

I have tried the latest version both on crates.io and github.

I get the following output (tried both debug and release):

RUST_BACKTRACE=1 cargo build --release
   Compiling secrets v0.12.1 (https://github.com/stouset/secrets#7c1d5ab8)
error: failed to run custom build command for `secrets v0.12.1 (https://github.com/stouset/secrets#7c1d5ab8)`

Caused by:
  process didn't exit successfully: `/root/memsecstore/target/release/build/secrets-f5777c6962e6309d/build-script-build` (exit code: 101)
--- stdout
cargo:rerun-if-env-changed=COVERAGE
cargo:rerun-if-env-changed=PROFILE
cargo:rustc-cfg=profile="release"
cargo:rerun-if-env-changed=LIBSODIUM_NO_PKG_CONFIG
cargo:rerun-if-env-changed=PKG_CONFIG
cargo:rerun-if-env-changed=LIBSODIUM_STATIC
cargo:rerun-if-env-changed=LIBSODIUM_DYNAMIC
cargo:rerun-if-env-changed=PKG_CONFIG_ALL_STATIC
cargo:rerun-if-env-changed=PKG_CONFIG_ALL_DYNAMIC
cargo:rerun-if-env-changed=PKG_CONFIG_PATH_x86_64-unknown-openbsd
cargo:rerun-if-env-changed=PKG_CONFIG_PATH_x86_64_unknown_openbsd
cargo:rerun-if-env-changed=HOST_PKG_CONFIG_PATH
cargo:rerun-if-env-changed=PKG_CONFIG_PATH
cargo:rerun-if-env-changed=PKG_CONFIG_LIBDIR_x86_64-unknown-openbsd
cargo:rerun-if-env-changed=PKG_CONFIG_LIBDIR_x86_64_unknown_openbsd
cargo:rerun-if-env-changed=HOST_PKG_CONFIG_LIBDIR
cargo:rerun-if-env-changed=PKG_CONFIG_LIBDIR
cargo:rerun-if-env-changed=PKG_CONFIG_SYSROOT_DIR_x86_64-unknown-openbsd
cargo:rerun-if-env-changed=PKG_CONFIG_SYSROOT_DIR_x86_64_unknown_openbsd
cargo:rerun-if-env-changed=HOST_PKG_CONFIG_SYSROOT_DIR
cargo:rerun-if-env-changed=PKG_CONFIG_SYSROOT_DIR
cargo:rerun-if-env-changed=LIBSODIUM_STATIC
cargo:rerun-if-env-changed=LIBSODIUM_DYNAMIC
cargo:rerun-if-env-changed=PKG_CONFIG_ALL_STATIC
cargo:rerun-if-env-changed=PKG_CONFIG_ALL_DYNAMIC
cargo:rerun-if-env-changed=PKG_CONFIG
cargo:rerun-if-env-changed=LIBSODIUM_STATIC
cargo:rerun-if-env-changed=LIBSODIUM_DYNAMIC
cargo:rerun-if-env-changed=PKG_CONFIG_ALL_STATIC
cargo:rerun-if-env-changed=PKG_CONFIG_ALL_DYNAMIC
cargo:rerun-if-env-changed=PKG_CONFIG_PATH_x86_64-unknown-openbsd
cargo:rerun-if-env-changed=PKG_CONFIG_PATH_x86_64_unknown_openbsd
cargo:rerun-if-env-changed=HOST_PKG_CONFIG_PATH
cargo:rerun-if-env-changed=PKG_CONFIG_PATH
cargo:rerun-if-env-changed=PKG_CONFIG_LIBDIR_x86_64-unknown-openbsd
cargo:rerun-if-env-changed=PKG_CONFIG_LIBDIR_x86_64_unknown_openbsd
cargo:rerun-if-env-changed=HOST_PKG_CONFIG_LIBDIR
cargo:rerun-if-env-changed=PKG_CONFIG_LIBDIR
cargo:rerun-if-env-changed=PKG_CONFIG_SYSROOT_DIR_x86_64-unknown-openbsd
cargo:rerun-if-env-changed=PKG_CONFIG_SYSROOT_DIR_x86_64_unknown_openbsd
cargo:rerun-if-env-changed=HOST_PKG_CONFIG_SYSROOT_DIR
cargo:rerun-if-env-changed=PKG_CONFIG_SYSROOT_DIR

--- stderr
thread 'main' panicked at 'called `Option::unwrap()` on a `None` value', src/libcore/option.rs:378:21
stack backtrace:
   0: __register_frame_info
   1: __register_frame_info
   2: __register_frame_info
   3: __register_frame_info
   4: __register_frame_info
   5: __register_frame_info
   6: __register_frame_info
   7: __register_frame_info
   8: __register_frame_info
   9: __register_frame_info
  10: __register_frame_info
  11: __register_frame_info
  12: __register_frame_info
  13: __register_frame_info
  14: __register_frame_info
  15: <unknown>

SIGSEGV on SecretVec::zero()

I am running secrets v0.11.1 and libsodium

$ apt-cache policy libsodium13
libsodium13:
  Installed: 1.0.1-1

and even a small program like

extern crate secrets;
use secrets::SecretVec;
fn main() {
    let secret = SecretVec::<u8>::zero(48);
}

segfaults with this backtrace:

Program received signal SIGSEGV, Segmentation fault.
0x00007ffff7ba5844 in sodium_memcmp () from /usr/lib/x86_64-linux-gnu/libsodium.so.13
(gdb) where
#0  0x00007ffff7ba5844 in sodium_memcmp ()
   from /usr/lib/x86_64-linux-gnu/libsodium.so.13
#1  0x00007ffff7ba5d0b in sodium_free ()
   from /usr/lib/x86_64-linux-gnu/libsodium.so.13
#2  0x000055555555b2fd in secrets::sodium::free<u8> (ptr=0x7ffff7ff2fd0 "")
    at /home/manuel/.cargo/registry/src/github.com-1ecc6299db9ec823/secrets-0.11.1/src/sodium.rs:51
#3  0x000055555555b0c5 in secrets::sec::{{impl}}::drop<u8> (self=0x7fffffffdf98)
    at /home/manuel/.cargo/registry/src/github.com-1ecc6299db9ec823/secrets-0.11.1/src/sec.rs:40
#4  0x000055555555aae1 in drop::h68a5af34c64127be ()
#5  0x000055555555ab29 in drop::h8ecd862ad2c8e15b ()
#6  0x000055555555b491 in xx::main () at /tmp/xx/src/main.rs:7
#7  0x000055555556c747 in __rust_maybe_catch_panic ()
#8  0x0000555555563a02 in std::rt::lang_start::haaae1186de9de8cb ()
#9  0x000055555555b514 in main ()

PS: Oh, and I am running rustc 1.13.0-nightly (55bf6a4f8 2016-09-18)

How do I handle Strings combined with Secret(Box)?

Hey!

Your examples mostly showcase usage with raw bytes. Could you help me out on working with a String that I want to securely store inside one of the Secret variants? The String is read from the tty (it's a user-entered password).

The SecretBox documentation showcases an example where some bytes are moved into SecretVec by using the from method. Is that currently the only way to accomplish this?

Thanks for helping me out!

is there protection agains gcode

Hello,

i tried the code and it looks like it does not protect against reading the process memory with gcore from gdb.

code:

use std::fs::File;
use std::io::Read;

use std::io::Error;

use std::time::Duration;
use std::thread::sleep;


use secrets::SecretBox;

const KEY_LEN : usize = 10;

fn main() -> Result<(), Error> {

    let mut key = SecretBox::<[u8; KEY_LEN]>::zero();
    File::open("key.txt")?
        .read_exact(key.borrow_mut().as_mut())?;

    let mut cnt = 0u32;

    loop {
        cnt += 1;

        sleep(Duration::from_secs(60));
        if cnt == 10 {
            break;
        }
    }
    Ok(())
}

I f you use

# gcore -a -o rust_secret_process_dump  $PID

you will find the content of key.txt with the strings tool in the generated
process memory dump.

# strings rust_secret_process_dump.$PID | grep (cat key.txt)

Maybe i missed that this class of attack is not what the library intends to protect from, but could someone elaborate on that.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.