Skip to main content

magic_cap_cli/
lib.rs

1//! # Magic Cap
2//!
3//! Provides low-level primitives for working with a "magic cap",
4//! which is a small string (~70 bytes) that can be turned back into
5//! the corresponding plaintext when presented alongside the correct
6//! ciphertext.
7//!
8//! The repository README has diagrams <https://github.com/magic-cap/magic-cap>
9//!
10//! <div class="warning">
11//! This is a release-early library that has <b>not yet received cryptographic (or other) audits</b>.
12//! We do appreciate feedback, but you own both pieces if you deploy to production :)
13//! </div>
14//!
15//! ## Overview
16//!
17//! Magic Cap turns the problem of having a lot of secret data
18//! (e.g. Sintel.mp4) into a tiny problem of only a little (fixed)
19//! amount of data ("the cap").
20//!
21//! The resulting (fixed, tiny) Magic Cap string can combined with the
22//! data file for the secret data; either part by itself cannot learn
23//! the secret data.
24//!
25//! The "Magic Cap" string is short (70 bytes) and can fit in TPMs or
26//! other secure storage.  Any interesting uses come when thinking
27//! about separating the Data (ciphertext + metadata) from the Magic
28//! Cap in time or space or both.
29//!
30//! ## Using the Crate
31//!
32//! This crate contains a CLI. For the Rust library, see:
33//! <https://docs.rs/magic_cap/latest/magic_cap/>
34//!
35//! Once built, you should have a binary called `mcap`, which
36//! will display help by default. It has several subcommands.
37//!
38//! ### `mcap encrypt`
39//!
40//! General usage is `mcap encrypt --plaintext <filename> <ciphertext-filename>`.
41//! For example:
42//!
43//! ```bash
44//!    $ mcap encrypt --plaintext kitten.jpeg kitten.mcap
45//!    mcap0r-Gshm9tyvjXDnfWpLWKMgjcK0AOdC-O12vvLW5rxeV7752pj2a2uogG4RpvMFS0g
46//! ```
47//!
48//! The string it spits out to stdout is the "Read Cap". The encrypted Data will
49//! be written to `kitten.mcap`. Later, you may decrypt them when presented together
50//!
51//! ### `mcap decrypt`
52//!
53//! Turn a Read Cap and Data back into plaintext. For example:
54//!
55//! ```bash
56//!    $ mcap decrypt --cap mcap0r-Gshm9tyvjXDnfWpLWKMgjcK0AOdC-O12vvLW5rxeV7752pj2a2uogG4RpvMFS0g --ciphertext kitten.mcap --plaintext kitten.jpeg
57//!    Wrote 306199 bytes of plaintext to "kitten.jpeg".
58//! ```
59//!
60//! You may get an error if the Read Cap does not correspond to the given ciphertext and exit-code 2.
61//!
62//! ```bash
63//!    $ mcap decrypt --cap mcap0rBX50S5FpIJQdu6cRr-bgGyxCzE9KHe46um1QcCfxn8PYZwX-X09Jv5I7vT1apgS6 --ciphertext kitten.mcap --plaintext kitten.jpeg
64//!    Error: Magic Cap does not correspond to Metadata hash
65//! ```
66//!
67//! ### `mcap reduce`
68//!
69//! Turn a Read Cap into a Verify Cap. A Verify Cap can confirm that a
70//! Data file is not corrupt and contains the correct ciphertext, but
71//! may not decrypt it. This could be used by a service provider or
72//! other third party to monitor or confirm availability of data
73//! without knowing what that data is.
74//!
75//! ```bash
76//!    $ mcap reduce mcap0r-Gshm9tyvjXDnfWpLWKMgjcK0AOdC-O12vvLW5rxeV7752pj2a2uogG4RpvMFS0g
77//!    mcap0v-Gshm7tyvjXDnfWpLWKMgjcK0AOdC-O12vvLW5rxeV4
78//! ```
79//!
80//! ### `mcap verify`
81//!
82//! Uses a Verify Cap to confirm that a Data file corresponds to it
83//! (and thus could be correctly decrypted by whomever has the Read
84//! Cap).
85//!
86//! ```bash
87//!    $ mcap verify --cap mcap0v-Gshm9tyvjXDnfWpLWKMgjcK0AOdC-O12vvLW5rxeV4 --ciphertext kitten.mcap
88//! ```
89//!
90//! (No output is good). An error may be printed (with exit code 2) if
91//! the Verify Cap does not correspond.
92//!
93//! ```bash
94//!    $ mcap verify --cap mcap0vBX50S5FpIJQdu6cRr-bgGyxCzE9KHe46um1QcCfxn8M --ciphertext kitten.mcap
95//!    Error: Magic Cap does not correspond to Metadata hash
96//! ```
97//!
98
99use magic_cap::err::MagicCapError;
100/// Functions that implement the core CLI commands
101use magic_cap::{
102    Immutable, ImmutableBuilder, ImmutableReadCap, ImmutableVerifier, ImmutableVerifyCap, ReadCap,
103};
104use std::fs::File;
105use std::io::prelude::*;
106use std::path::{Path, PathBuf};
107
108/// Implementation of "mcap encrypt"
109/// This is the top level function for easy use of this crate by applications or other libraries.
110/// This function does not consider memory use, but instead just does the thing using all the memory.
111pub fn main_encrypt(
112    output: &mut impl Write,
113    plain_text: &Path,
114    output_fname: &Path,
115) -> Result<(), MagicCapError> {
116    let mut input_file = std::fs::File::open(plain_text)?;
117
118    // "write as one file"
119    // write "mcap"
120    // write 4-byte version number (1?)
121    // write all the ciphertext blocks
122    // write the metadata
123    // write 8-byte offset to metadata
124    // done
125
126    // file format version 1 is:
127    // 4 bytes: "mcap"
128    // 4 bytes: 0x01  (the version number, big-endian)
129    // <all the ciphertext blocks>
130    // <meta_offset>: <metadata>
131    // 8 bytes: u64 meta_offset to start of metadata
132
133    let output_file = File::create(output_fname)?;
134    let mut bufw = std::io::BufWriter::new(output_file);
135
136    let mut plaintext: Vec<u8> = vec![0u8; 4096];
137    let mut cryptor = ImmutableBuilder::new(4096, &mut bufw)?;
138
139    let mut r = input_file.read(&mut plaintext)?;
140    while r != 0 {
141        plaintext.resize(r, 0);
142        let _ = cryptor.write(&plaintext)?;
143        r = input_file.read(&mut plaintext)?;
144    }
145    let (cap, _) = cryptor.done()?;
146
147    let capstr = format!("{}", cap);
148    writeln!(output, "{}", capstr)?;
149    Ok(())
150}
151
152/// "mcap decrypt"
153pub fn main_decrypt(
154    //    input: &mut impl Read,
155    output: &mut impl Write,
156    cap: &str,
157    input_fname: &PathBuf,
158    outfile: &Path,
159) -> Result<(), MagicCapError> {
160    let cap = ImmutableReadCap::try_from(cap)?;
161    let f = std::fs::File::open(input_fname)?;
162    let imm = Immutable::read(&mut std::io::BufReader::new(f))?;
163
164    match cap.decrypt(&imm) {
165        Ok(plain) => {
166            let mut out = std::fs::File::create(outfile)?;
167            out.write_all(plain.as_slice())?;
168            match outfile.to_str() {
169                Some(of) => {
170                    writeln!(
171                        output,
172                        "Wrote {} bytes of plaintext to \"{}\".",
173                        plain.len(),
174                        of,
175                    )?;
176                    Ok(())
177                }
178                None => Ok(()),
179            }
180        }
181        Err(e) => match &e {
182            MagicCapError::McapMetadataDiscordant() => Err(e),
183            _ => {
184                writeln!(output, "Error decrypting: {}", e)?;
185                Ok(())
186            }
187        },
188    }
189}
190
191/// "mcap verify"
192pub fn main_verify(cap: &str, input_fname: &Path) -> Result<(), MagicCapError> {
193    let cap = ImmutableVerifyCap::try_from(cap)?;
194    let f = std::fs::File::open(input_fname)?;
195    let imm = Immutable::read(&mut std::io::BufReader::new(f))?;
196
197    cap.verify(&imm)?;
198    Ok(())
199}
200
201/// "mcap reduce"
202pub fn main_reduce(output: &mut impl Write, cap: &str) -> Result<(), MagicCapError> {
203    if let Ok(readcap) = ImmutableReadCap::try_from(cap) {
204        let verifycap = ImmutableVerifyCap::from(readcap);
205        writeln!(output, "{}", verifycap)?;
206    } else if let Ok(verifycap) = ImmutableVerifyCap::try_from(cap) {
207        writeln!(output, "{}", verifycap)?;
208    } else {
209        writeln!(output, "Unknown kind of cap.")?;
210        return Err(MagicCapError::InvalidCap(cap.to_string()));
211    }
212    Ok(())
213}
214
215#[cfg(test)]
216pub mod test {
217    use super::*;
218    use proptest::prelude::*;
219    use tempfile::tempdir;
220
221    proptest! {
222        #[test]
223        fn round_trip_main(s in "\\PC+") {
224            // write to a file so we can exercise via paths
225            let outd = tempdir()?;
226            let plain = outd.path().join("plain");
227            {
228                let mut tmp = File::create(&plain)?;
229                tmp.write(s.as_bytes())?;
230            }  // close tmp
231            let cipher = outd.path().join("cipher");
232            let mut output = vec!();
233            main_encrypt(&mut output, &plain, &cipher).unwrap();
234
235            let capstr: &str = std::str::from_utf8(&output)?.trim_end();
236            let round = outd.path().join("decrypted");
237
238            // turn this into a Verify Cap and confirm the ciphertext
239            let mut output = vec!();
240            main_reduce(&mut output, capstr)?;
241            let verifycap = std::str::from_utf8(&output)?.trim_end();
242            main_verify(verifycap, &cipher).unwrap();
243
244            // confirm that "decrypt" can turn back into plaintext
245            let mut output = vec!();
246            main_decrypt(&mut output, capstr, &cipher, &round).unwrap();
247
248            let mut og = String::new();
249            let mut other = String::new();
250            File::open(plain)?.read_to_string(&mut og)?;
251            File::open(round)?.read_to_string(&mut other)?;
252
253            assert_eq!(og, other);
254        }
255    }
256}