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}