nuts_tool/cli/container/
create.rs

1// MIT License
2//
3// Copyright (c) 2023,2024 Robin Doer
4//
5// Permission is hereby granted, free of charge, to any person obtaining a copy
6// of this software and associated documentation files (the "Software"), to
7// deal in the Software without restriction, including without limitation the
8// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
9// sell copies of the Software, and to permit persons to whom the Software is
10// furnished to do so, subject to the following conditions:
11//
12// The above copyright notice and this permission notice shall be included in
13// all copies or substantial portions of the Software.
14//
15// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
20// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
21// IN THE SOFTWARE.
22
23use anyhow::{ensure, Result};
24use clap::{value_parser, ArgAction, Args};
25use log::debug;
26use nuts_container::{Cipher, Container, CreateOptionsBuilder, Kdf};
27use nuts_tool_api::tool::Plugin;
28use std::cell::RefCell;
29use std::os::fd::RawFd;
30use std::path::PathBuf;
31
32use crate::backend::{PluginBackend, PluginBackendCreateBuilder};
33use crate::cli::container::{CliCipher, AES256_GCM};
34use crate::cli::global::PasswordSource;
35use crate::cli::password::password_from_source_twice;
36use crate::config::{ContainerConfig, PluginConfig};
37
38thread_local! {
39    static SOURCE: RefCell<PasswordSource> = RefCell::new(Default::default());
40}
41
42fn password_callback() -> Result<Vec<u8>, String> {
43    SOURCE.with_borrow(|src| password_from_source_twice(src, "Enter a password"))
44}
45
46#[derive(Args, Debug)]
47pub struct ContainerCreateArgs {
48    /// The  name of the new container
49    name: String,
50
51    /// Specifies the plugin used by the new container
52    #[clap(short, long)]
53    plugin: String,
54
55    /// Sets the cipher to CIPHER.
56    #[clap(short, long, value_parser = value_parser!(CliCipher), default_value = AES256_GCM)]
57    cipher: CliCipher,
58
59    /// Specifies the key derivation function.
60    ///
61    /// There are two ways to specify the KDF. The short form
62    /// only specifies the algorithm name. The long form can
63    /// customize the algorithm; it starts with the algorithm
64    /// name followed by sections separated by a colon. A section
65    /// can empty. In this case a default value is taken. The
66    /// number of sections and its meaning depends on the
67    /// algorithm.
68    ///
69    /// For PBKDF2: pbkdf2[:[<DIGEST>]:[<ITERATIONS>]:[<SALT_LENGTH>]]
70    ///
71    /// Selects PBKDF2 with the given digest (default: sha256),
72    /// the given number of iterations (default: 65536) and salt
73    /// length (default: 16).
74    #[clap(short, long, value_parser)]
75    kdf: Option<Kdf>,
76
77    /// If set, overwrites an existing container
78    #[clap(short, long, action = ArgAction::SetTrue)]
79    overwrite: bool,
80
81    /// Arguments passed to the plugin
82    #[clap(value_name = "PLUGIN ARGS")]
83    plugin_args: Vec<String>,
84
85    #[clap(from_global)]
86    verbose: u8,
87
88    #[clap(from_global)]
89    password_from_fd: Option<RawFd>,
90
91    #[clap(from_global)]
92    password_from_file: Option<PathBuf>,
93}
94
95impl ContainerCreateArgs {
96    pub fn run(&self) -> Result<()> {
97        debug!("args: {:?}", self);
98
99        let plugin_config = PluginConfig::load()?;
100        let mut container_config = ContainerConfig::load()?;
101
102        let exe = plugin_config.path(&self.plugin)?;
103        let plugin = Plugin::new(&exe);
104
105        let ok = container_config.add_plugin(&self.name, &self.plugin, self.overwrite);
106        ensure!(
107            ok,
108            "you already have a container with the name {}",
109            self.name
110        );
111
112        SOURCE.with_borrow_mut(|src| {
113            *src = PasswordSource::new(self.password_from_fd, self.password_from_file.clone())
114        });
115
116        let backend_options =
117            PluginBackendCreateBuilder::new(plugin, &self.name, self.verbose, &self.plugin_args)?;
118        let mut builder = CreateOptionsBuilder::new(*self.cipher)
119            .with_password_callback(password_callback)
120            .with_overwrite(self.overwrite);
121
122        if self.cipher != Cipher::None {
123            if let Some(kdf) = self.kdf.clone() {
124                debug!("kdf: {:?}", kdf);
125                builder = builder.with_kdf(kdf);
126            }
127        }
128
129        let options = builder.build::<PluginBackend>()?;
130        Container::<PluginBackend>::create(backend_options, options)?;
131
132        container_config.save()?;
133
134        Ok(())
135    }
136}